ZyX_I avatar ZyX_I committed 28bf1b3

@/*: Moved everything except plugin/frawor to autoload
@/python: Removed _r.py resource, made it support case “both pythons are present”
For python3 now python3 directory is used, using python3 modules from python/ is not supported.
Fixes #3

Comments (0)

Files changed (204)

autoload/frawor/autocommands.vim

+"▶1 Header
+scriptencoding utf-8
+execute frawor#Setup('0.0', {})
+"▶1 Define messages
+if v:lang=~?'ru'
+    let s:_messages={
+                \   'agidnstr': 'Ошибка создания группы событий '.
+                \               'для дополнения %s: название группы '.
+                \               'не является строкой',
+                \    'invagid': 'Ошибка создания группы событий '.
+                \               'для дополнения %s: строка «%s» не может '.
+                \               'являться названием группы',
+                \   'loadfail': 'Не удалось загрузить дополнение %s',
+                \  'dagidnstr': 'Ошибка удаления группы событий '.
+                \               'для дополнения %s: название группы '.
+                \               'не является строкой',
+                \    'uknagid': 'Группа событий «%s» не определена или '.
+                \               'определена не дополнением %s',
+            \}
+    call extend(s:_messages, map({
+                \    'agiddef': 'группа уже определена дополнением %s',
+                \    'agenlst': 'список событий не является списком',
+                \   'noevents': 'отсутствуют события в списке',
+                \      'enlst': 'одно из событий не является списком',
+                \     'en3lst': 'одно из событий содержит не три элемента',
+            \},'"Ошибка создания группы событий %s для дополнения %s: ".v:val'))
+    call extend(s:_messages, map({
+                \ 'etypenslst': 'тип события не является строкой или списком',
+                \  'invetypes': 'часть типов событий не верна',
+                \    'ptrnstr': 'шаблон не является строкой',
+                \  'nestnbool': 'третий элемент не является единицей или нулём',
+                \   'emptycmd': 'список аргументов пуст',
+                \ 'nowrapfunc': 'отсутствует функция _f.wrapfunc '.
+                \               '(дополнение должно зависеть от '.
+                \                'plugin/frawor/functions)',
+                \     'ukncmd': 'не удалось обработать команду',
+            \},'"Ошибка создания события №%u группы событий %s '.
+            \   'для дополнения %s: ".v:val'))
+else
+    let s:_messages={
+                \   'agidnstr': 'Error while creating augroup for plugin %s: '.
+                \               'group ID is not a String',
+                \    'invagid': 'Error while creating augroup for plugin %s: '.
+                \               'string `%s'' is not a valid group ID',
+                \   'loadfail': 'Failed to load plugin %s',
+                \  'dagidnstr': 'Error while deleting augroup for plugin %s: '.
+                \               'group ID is not a String',
+                \    'uknagid': 'Augroup `%s'' is either undefined or '.
+                \               'defined not by plugin %s',
+            \}
+    call extend(s:_messages, map({
+                \    'agiddef': 'group was already defined by plugin %s',
+                \    'agenlst': 'event list is not a List',
+                \   'noevents': 'no events in list',
+                \      'enlst': 'one of events is not a List',
+                \     'en3lst': 'one of events contains not 3 elements',
+            \}, '"Error while creating augroup %s for plugin %s: ".v:val'))
+    call extend(s:_messages, map({
+                \ 'etypenslst': 'event type is neither String nor List',
+                \  'invetypes': 'some event types are not valid',
+                \    'ptrnstr': 'event pattern is not a String',
+                \  'nestnbool': 'third list element is neither 0 nor 1',
+                \   'emptycmd': 'arguments list is empty',
+                \ 'nowrapfunc': 'function _f.wrapfunc is absent '.
+                \               '(plugin must depend on '.
+                \                'plugin/frawor/functions)',
+                \     'ukncmd': 'failed to process command',
+            \}, '"Error while processing event #%u for augroup %s '.
+            \    'defined by plugin %s: ".v:val'))
+endif
+"▶1 wipeau       :: agname → + :autocmd
+function s:F.wipeau(agname)
+    execute 'augroup' a:agname
+        autocmd!
+    augroup END
+    execute 'augroup!' a:agname
+endfunction
+"▶1 add_augroups :: {f} → + p:_augroups
+function s:F.add_augroups(plugdict, fdict)
+    if !has_key(a:plugdict.g, '_augroups') ||
+                \type(a:plugdict.g._augroups)!=type([])
+        let a:plugdict.g._augroups=[]
+    endif
+endfunction
+"▶1 delaugroups  :: {f} + p:_augroups → + :autocmd, p:_augroups
+function s:F.delaugroups(plugdict, fdict)
+    if !has_key(a:plugdict.g, '_augroups') ||
+                \type(a:plugdict.g._augroups)!=type([])
+        return
+    endif
+    let d={}
+    call map(filter(copy(a:plugdict.g._augroups),
+                \   'type(v:val)=='.type('').' && stridx(v:val, "#")==-1 && '.
+                \   'exists("#".v:val)'),
+                \'s:F.wipeau(v:val)')
+endfunction
+call s:_f.newfeature('delaugroups', {'unloadpre': s:F.delaugroups,
+            \                         'register': s:F.add_augroups,
+            \                       'ignoredeps': 1})
+"▶1 augroup feature
+let s:F.augroup={}
+let s:augroups={}
+"▶2 aurun        :: agid, eidx → + ?
+function s:F.aurun(agid, eidx)
+    let augroup=s:augroups[a:agid]
+    let args=get(augroup.args, a:eidx, [])
+    if type(augroup.funcs[a:eidx])==type({})
+        if !FraworLoad(augroup.plid)
+            call s:_f.throw('loadfail', augroup.plid)
+        endif
+        let augroup.funcs[a:eidx]=augroup.wrapfunc(augroup.funcs[a:eidx])
+    endif
+    return call(augroup.funcs[a:eidx], args, {})
+endfunction
+"▶2 createau     :: augroup → + :autocmd
+function s:F.createau(augroup)
+    execute 'augroup' a:augroup.agname
+        autocmd!
+        for auargs in a:augroup.events
+            execute 'autocmd' auargs
+        endfor
+    augroup END
+endfunction
+"▶2 augroup.del  :: {f}[, agid] → + :autocmd
+function s:F.augroup.del(plugdict, fdict, ...)
+    if a:0
+        if type(a:1)!=type('')
+            call s:_f.throw('dagidnstr', a:plugdict.id)
+        elseif !has_key(a:fdict, a:1)
+            call s:_f.throw('uknagid', a:1, a:plugdict.id)
+        endif
+        call s:F.wipeau(a:fdict[a:1].agname)
+        unlet a:fdict[a:1]
+        unlet s:augroups[a:1]
+    else
+        call map(values(a:fdict), 's:F.wipeau(v:val.agname)')
+        call map(keys(a:fdict), 'remove(s:augroups, v:val)')
+    endif
+endfunction
+"▶2 augroup.add  :: {f}, agid, [event] → + :autocmd, s:augroups
+function s:F.augroup.add(plugdict, fdict, agid, events)
+    "▶3 Check arguments
+    if type(a:agid)!=type('')
+        call s:_f.throw('agidnstr', a:plugdict.id)
+    elseif a:agid!~#'^\w\+$'
+        call s:_f.throw('invagid', a:plugdict.id, a:agid)
+    elseif has_key(s:augroups,a:agid) && s:augroups[a:agid].plid isnot# a:plugdict.id
+        call s:_f.throw('agiddef', a:agid,a:plugdict.id,s:augroups[a:agid].plid)
+    elseif type(a:events)!=type([])
+        call s:_f.throw('agenlst', a:agid, a:plugdict.id)
+    elseif empty(a:events)
+        call s:_f.throw('noevents', a:agid, a:plugdict.id)
+    elseif !empty(filter(copy(a:events), 'type(v:val)!='.type([])))
+        call s:_f.throw('enlst', a:agid, a:plugdict.id)
+    elseif !empty(filter(copy(a:events), 'len(v:val)!=4'))
+        call s:_f.throw('en3lst', a:agid, a:plugdict.id)
+    endif
+    "▲3
+    if has_key(s:augroups, a:agid)
+        let augroup=s:augroups[a:agid]
+    else
+        let augroup   =   {'id': a:agid,
+                    \    'plid': a:plugdict.id,
+                    \  'agname': 'FraworAugroup_'.a:agid,
+                    \  'events': [],
+                    \   'funcs': {},
+                    \    'args': {}}
+    endif
+    let i=len(augroup.events)
+    let d={}
+    for [d.event, d.pattern, d.nested, d.command] in a:events
+        let epc=[]
+        if type(d.event)==type('')
+            call add(epc, split(d.event, ','))
+        elseif type(d.event)==type([])
+            call add(epc, d.event)
+        "▶3 Invalid event types
+        else
+            call s:_f.throw('etypenslst', i, a:agid, a:plugdict.id)
+        endif
+        if !empty(filter(copy(epc[0]), 'type(v:val)!='.type('').' || '.
+                    \                  'v:val=~#"\\_[^a-zA-Z]" || '.
+                    \                  '!exists("##".v:val)'))
+            call s:_f.throw('invetypes', i, a:agid, a:plugdict.id)
+        endif
+        "▲3
+        let epc[0]=join(epc[0], ',')
+        "▶3 Check pattern
+        if type(d.pattern)!=type('')
+            call s:_f.throw('ptrnstr', i, a:agid, a:plugdict.id)
+        endif
+        "▲3
+        call add(epc, escape(d.pattern, ' '))
+        "▶3 Check nested
+        if d.nested isnot 0 && d.nested isnot 1
+            call s:_f.throw('nestnbool', i, a:agid, a:plugidct.id)
+        endif
+        "▲3
+        if d.nested
+            call add(epc, 'nested')
+        endif
+        if type(d.command)==type([])
+            "▶3 Check d.command
+            if empty(d.command)
+                call s:_f.throw('emptycmd', i, a:agid, a:plugdict.id)
+            endif
+            "▲3
+            let [d.command; augroup.args[i]]=d.command
+        endif
+        if exists('*d.command')
+            let augroup.funcs[i]=d.command
+            if has_key(augroup.args, i) && !empty(augroup.args[i])
+                let epc+=['call call(s:augroups.'.a:agid.'.funcs.'.i.', '.
+                            \       's:augroups.'.a:agid.'.args.'.i.', {})']
+            else
+                let epc+=['call s:augroups.'.a:agid.'.funcs.'.i.'()']
+            endif
+        elseif type(d.command)==type({})
+            "▶3 Check for wrapfunc
+            if !exists('*a:plugdict.g._f.wrapfunc')
+                call s:_f.throw('nowrapfunc', i, a:agid, a:plugdict.id)
+            endif
+            "▲3
+            let augroup.wrapfunc=a:plugdict.g._f.wrapfunc
+            let augroup.funcs[i]=d.command
+            let epc+=['call s:F.aurun('.string(a:agid).', '.i.')']
+        elseif !has_key(augroup.args, i) && type(d.command)==type('')
+            call add(epc, substitute(d.command, '<SID>',
+                        \            '<SNR>'.a:plugdict.sid.'_', 'g'))
+        "▶3 Unknown d.command
+        else
+            call s:_f.throw('ukncmd', i, a:agid, a:plugdict.id)
+        endif
+        "▲3
+        call add(augroup.events, join(epc))
+        let i+=1
+        unlet d.event d.pattern d.command
+    endfor
+    let s:augroups[augroup.id]=augroup
+    let a:fdict[augroup.id]=augroup
+    call s:F.createau(augroup)
+endfunction
+"▶2 Define feature
+call s:_f.newfeature('augroup', {'cons': s:F.augroup,
+            \                  'unload': s:F.augroup.del,})
+"▶1
+call frawor#Lockvar(s:, 'augroups')
+" vim: fmr=▶,▲ sw=4 ts=4 sts=4 et tw=80

autoload/frawor/base64.vim

+"▶1 Header
+scriptencoding utf-8
+execute frawor#Setup('0.0', {'@/resources': '0.0'})
+let s:F.base64={}
+"▶1 and                :: UInt, UInt → UInt
+if exists('*and')
+    let s:F.and=function('and')
+else
+    function s:F.and(v1, v2)
+        let [v1, v2]=[a:v1, a:v2]
+        let list=[]
+        while v1 || v2
+            let [nv1, nv2]=[v1/2, v2/2]
+            call add(list, ((nv1*2!=v1)&&(nv2*2!=v2)))
+            let [v1, v2]=[nv1, nv2]
+        endwhile
+        let r=0
+        while !empty(list)
+            let r=(r*2) + remove(list, -1)
+        endwhile
+        return r
+    endfunction
+endif
+"▶1 or                 :: UInt, UInt → UInt
+if exists('*or')
+    let s:F.or=function('or')
+else
+    function s:F.or(v1, v2)
+        let [v1, v2]=[a:v1, a:v2]
+        let list=[]
+        while v1 || v2
+            let [nv1, nv2]=[v1/2, v2/2]
+            call add(list, ((nv1*2!=v1)||(nv2*2!=v2)))
+            let [v1, v2]=[nv1, nv2]
+        endwhile
+        let r=0
+        while !empty(list)
+            let r=(r*2) + remove(list, -1)
+        endwhile
+        return r
+    endfunction
+endif
+"▶1 base64.decode      :: b64str[, bytearray::Bool] → str | bytearray
+let s:cd64=map(split('|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\]^_`abcdefghijklmnopq',
+            \              '\v.@='),
+            \        'char2nr(v:val)')
+function s:F.base64.decode(str, ...)
+    let str=map(split(substitute(a:str, '[^a-zA-Z0-9+/]', '', 'g'), '\v.@='),
+                \'char2nr(v:val)')+[-1]
+    let in=repeat([0], 4)
+    let v=0
+    let len=0
+    let i=0
+    let bytearray=(a:0 && a:1)
+    if bytearray
+        let r=[]
+    else
+        let r=''
+    endif
+    while !empty(str)
+        let i=0
+        let len=0
+        while i<4 && !empty(str)
+            let v=0
+            while !empty(str) && v==0
+                let v=remove(str, 0)
+                let v=(((v<43)||(v>122))?(0):(s:cd64[v-43]))
+                if v
+                    let v=((v==36)?(0):(v-61))
+                endif
+            endwhile
+            if !empty(str)
+                let len+=1
+                if v
+                    let in[i]=v-1
+                endif
+            else
+                let in[i]=0
+            endif
+            let i+=1
+        endwhile
+        if len
+            let out=[    s:F.or(        in[0]*4,         in[1]/16),
+                        \s:F.or(        in[1]*16,        in[2]/4),
+                        \s:F.or(s:F.and(in[2]*64, 0xC0), in[3])]
+            call map(out, 's:F.and(v:val, 0xFF)')
+            if bytearray
+                let r+=out[:(len-2)]
+            else
+                let r.=join(map(out[:(len-2)],
+                            \   'eval(printf(''"\x%02x"'', v:val))'),
+                            \'')
+            endif
+        endif
+    endwhile
+    return r
+endfunction
+"▶1 base64.encode      :: str | bytearray → b64str
+let s:eqsigncode=char2nr('=')
+let s:cb64=map(split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
+            \        '\v.@='), 'char2nr(v:val)')
+function s:F.base64.encode(str)
+    let r=''
+    let bytearray=(type(a:str)==type([]))
+    let in=repeat([0], 3)
+    let idx=0
+    let slen=len(a:str)
+    while idx<slen
+        let len=0
+        let i=0
+        while i<3
+            if idx<slen
+                let cur=a:str[idx]
+                if !bytearray
+                    let cur=char2nr(cur)
+                endif
+                let in[i]=cur
+                let len+=1
+                let idx+=1
+            else
+                let in[i]=0
+            endif
+            let i+=1
+        endwhile
+        if len
+            let out=[    s:cb64[in[0]/4],
+                        \s:cb64[s:F.or(((s:F.and(in[0], 0x03))*16),
+                        \              ((s:F.and(in[1], 0xF0))/16))],
+                        \((len>1)?
+                        \   (s:cb64[s:F.or(s:F.and(in[1], 0x0F)*4,
+                        \                  s:F.and(in[2], 0xC0)/64)]):
+                        \   (s:eqsigncode)),
+                        \((len>2)?
+                        \   (s:cb64[s:F.and(in[2], 0x3F)]):
+                        \   (s:eqsigncode))]
+            let r.=join(map(copy(out), 'eval(printf(''"\x%02x"'', v:val))'), '')
+        endif
+    endwhile
+    return r
+endfunction
+"▶1 base64.encodelines :: [string] → b64str
+function s:F.base64.encodelines(lines)
+    let bytes=[]
+    let i=0
+    let lls=len(a:lines)
+    while i<lls
+        let j=0
+        let ll=len(a:lines[i])
+        while j<ll
+            let byte=char2nr(a:lines[i][j])
+            if byte==10 " NL
+                call add(bytes, 0)
+            else
+                call add(bytes, byte)
+            endif
+            let j+=1
+        endwhile
+        call add(bytes, 10)
+        let i+=1
+    endwhile
+    call remove(bytes, -1) " Remove last NL
+    return s:F.base64.encode(bytes)
+endfunction
+"▶1 base64.decodelines :: b64str → [string]
+function s:F.base64.decodelines(b64str)
+    let r=['']
+    for byte in s:F.base64.decode(a:b64str, 1)
+        if byte==10
+            call add(r, '')
+        elseif byte==0
+            let r[-1].="\n"
+        else
+            let r[-1].=eval(printf('"\x%02x"', byte))
+        endif
+    endfor
+    return r
+endfunction
+"▶1 post resource
+call s:_f.postresource('base64', s:F.base64)
+"▶1
+call frawor#Lockvar(s:, '')
+" vim: fmr=▶,▲ sw=4 ts=4 sts=4 et tw=80

autoload/frawor/checks.vim

+"▶1 Header
+scriptencoding utf-8
+execute frawor#Setup('0.0', {'@/fwc': '0.0',
+            \         '@/decorators': '0.0'})
+"▶1 _messages
+if v:lang=~?'ru'
+    let s:_messages={
+                \'uchecker': 'Ошибка создания проверки для дополнения %s: '.
+                \            'аргумент, описывающий проверку, '.
+                \            'не принадлежит ни к одному из известных типов',
+                \'chkncall': 'Ошибка создания проверки для дополнения %s: '.
+                \            'проверочная функция не может быть вызвана '.
+                \            '(возможно вы использовали ссылку на внутренную '.
+                \            'функцию дополнения без раскрытия «s:» '.
+                \            'в «<SNR>_{SID}»?)',
+                \ 'ufilter': 'Ошибка создания фильтра для дополнения %s: '.
+                \            'аргумент, описывающий фильтр, '.
+                \            'не принадлежит ни к одному из известных типов',
+                \'filncall': 'Ошибка создания фильтра для дополнения %s: '.
+                \            'проверочная функция не может быть вызвана '.
+                \            '(возможно вы использовали ссылку на внутренную '.
+                \            'функцию дополнения без раскрытия «s:» '.
+                \            'в «<SNR>_{SID}»?)',
+                \'idnotstr': 'Ошибка при обработке запроса на удаление от '.
+                \            'дополнения %s: идентификатор не является строкой',
+            \}
+else
+    let s:_messages={
+                \'uchecker': 'Error while creating checker for plugin %s: '.
+                \            'unknown check description type',
+                \'chkncall': 'Error while creating checker for plugin %s: '.
+                \            '(perhaps you tried to use a reference to '.
+                \            'a script-local function without replacing `s:'' '.
+                \            'with `<SNR>_{SID}'')',
+                \ 'ufilter': 'Error while creating filter for plugin %s: '.
+                \            'unknown filter description type',
+                \'filncall': 'Error while creating filter for plugin %s: '.
+                \            '(perhaps you tried to use a reference to '.
+                \            'a script-local function without replacing `s:'' '.
+                \            'with `<SNR>_{SID}'')',
+                \'idnotstr': 'Error while processing delete request from '.
+                \            'plugin %s: ID is not a string',
+            \}
+endif
+"▶1 freeres      :: {f}
+function s:F.freeres(plugdict, fdict)
+    call map(a:fdict.FWCids, 's:_f.fwc.del(v:val)')
+endfunction
+"▶1 conschecker feature
+"▶2 conschecker  :: {f}, checker[, gdict] → chkfunc + ?
+function s:F.conschecker(plugdict, fdict, Chk, ...)
+    if type(a:Chk)==2
+        if !exists('*a:Chk')
+            call s:_f.throw('chkncall', a:plugdict.id)
+        endif
+        return a:Chk
+    elseif type(a:Chk)==type('')
+        let d={}
+        let [d.F, id]=s:_f.fwc.compile(a:Chk, 'check',
+                    \                  get(a:000, 0, a:plugdict.g))
+        call add(a:fdict.FWCids, id)
+        return d.F
+    else
+        call s:_f.throw('uchecker', a:plugdict.id)
+    endif
+endfunction
+"▶2 Register feature
+call s:_f.newfeature('conschecker', {'cons': s:F.conschecker,
+            \                      'unload': s:F.freeres,
+            \                        'init': {'FWCids': []}})
+"▶1 consfilter feature
+"▶2 consfilter  :: {f}, filter[, gdict] → filfunc + ?
+function s:F.consfilter(plugdict, fdict, Fil, ...)
+    if type(a:Fil)==2
+        if !exists('*a:Fil')
+            call s:_f.throw('filncall', a:plugdict.id)
+        endif
+        return a:Fil
+    elseif type(a:Fil)==type('')
+        let d={}
+        let [d.F, id]=s:_f.fwc.compile(a:Fil, 'filter',
+                    \                  get(a:000, 0, a:plugdict.g))
+        call add(a:fdict.FWCids, id)
+        return d.F
+    else
+        call s:_f.throw('ufilter', a:plugdict.id)
+    endif
+endfunction
+"▶2 Register feature
+call s:_f.newfeature('consfilter', {'cons': s:F.consfilter,
+            \                     'unload': s:F.freeres,
+            \                       'init': {'FWCids': []}})
+"▶1 Decorators: checker and filter
+let s:F.de={}
+"▶2 checker
+function s:F.de.checker(plugdict, fname, Arg)
+    let throwargs="'".a:fname."', ".
+                \substitute(string(a:plugdict.id), "\n", '''."\\n".''', 'g')
+    return [128, '@@@', a:plugdict.g._f.conschecker(a:Arg),
+                \['if !@%@(@@@)',
+                \     'call s:_f.throw("checkfailed", '.throwargs.')',
+                \ 'endif',], [], 0]
+endfunction
+call s:_f.adddecorator('checker', s:F.de.checker)
+"▶2 filter
+function s:F.de.filter(plugdict, fname, Arg)
+    let throwargs="'".a:fname."', ".
+                \substitute(string(a:plugdict.id), "\n", '''."\\n".''', 'g')
+    return [64, 'args', a:plugdict.g._f.consfilter(a:Arg),
+                \['let args=@%@(@@@)',
+                \ 'if type(args)!='.type([]),
+                \ '    call s:_f.throw("filterfailed", '.throwargs.')',
+                \ 'endif',], [], 0]
+endfunction
+call s:_f.adddecorator('filter', s:F.de.filter)
+"▲2
+unlet s:F.de
+"▶1
+call frawor#Lockvar(s:, 'checkers,filters')
+" vim: fmr=▶,▲ sw=4 ts=4 sts=4 et tw=80

autoload/frawor/commands.vim

+"▶1 Header
+scriptencoding utf-8
+execute frawor#Setup('0.0', {'@/autocommands': '0.0',
+            \                         '@/fwc': '0.0',})
+"▶1 Define messages
+if v:lang=~?'ru'
+    let s:_messages={
+                \   'cidnstr': 'Ошибка создания команды для дополнения %s: '.
+                \              'имя команды не является строкой',
+                \    'invcid': 'Ошибка создания команды для дополнения %s: '.
+                \              'строка «%s» не может являться именем команды',
+                \  'dcidnstr': 'Ошибка удаления команды для дополнения %s: '.
+                \              'имя команды не является строкой',
+                \   'nowncid': 'Ошибка удаления команды для дополнения %s: '.
+                \              'команда «%s» не была определена этим '.
+                \              'дополнением',
+            \}
+    call extend(s:_messages, map({
+                \    'ciddef': 'команда уже определена дополнением %s',
+                \   'cidedef': 'команда уже определена',
+                \ 'coptsndct': 'второй аргумент не является словарём',
+                \  'invrange': '«%s» не является правильным диапозоном',
+                \  'hascount': 'нельзя использовать «range» и «count» вместе',
+                \  'invcount': '«%s» не является числом',
+                \    'invkey': 'ключ %s не принимает никаких аргументов',
+                \  'invnargs': '%s не является правильным описанием числа '.
+                \              'параметров',
+                \   'invclen': 'неправильное количество аргументов',
+                \     '1nstr': 'аргумент не является строкой на языке FWC',
+                \     'ucomp': 'непонятное описание функции автодополнения',
+                \   'invsreg': '«%s» не является правильным регулярным '.
+                \              'выражением: %s',
+                \     'invsp': 'неверное значение ключа «%ssplitfunc»',
+                \'nowrapfunc': 'отсутствует функция _f.wrapfunc '.
+                \              '(дополнение должно зависеть от '.
+                \               'plugin/frawor/functions)',
+                \     'urepl': 'непонятное описание команды',
+            \}, '"Ошибка создания команды %s для дополнения %s: ".v:val'))
+else
+    let s:_messages={
+                \   'cidnstr': 'Error while creating command for plugin %s: '.
+                \              'command name is not a String',
+                \    'invcid': 'Error while creating command for plugin %s: '.
+                \              '`%s'' is not a valid command name',
+                \  'dcidnstr': 'Error while deleting command for plugin %s: '.
+                \              'command name is not a String',
+                \   'nowncid': 'Error while deleting command for plugin %s: '.
+                \              '`%s'' was not defined by this plugin',
+            \}
+    call extend(s:_messages, map({
+                \    'ciddef': 'command was already defined by plugin %s',
+                \   'cidedef': 'command was already defined',
+                \ 'coptsndct': 'second argument is not a Dictionary',
+                \  'invrange': '`%s'' is not a valid range',
+                \  'hascount': 'cannot use both `range'' and `count'' '.
+                \              'for one command',
+                \  'invcount': '`%s'' is not a valid count',
+                \    'invkey': 'key %s does not accept any arguments',
+                \  'invnargs': '%s is not a valid parameter number description',
+                \   'invclen': 'invalid number of arguments',
+                \     '1nstr': 'expected FWC string here',
+                \     'ucomp': 'invalid completion description',
+                \   'invsreg': '`%s'' is not a valid regular expression: %s',
+                \     'invsp': 'invalid value of `%ssplitfunc'' key',
+                \'nowrapfunc': 'function _f.wrapfunc is absent (plugin '.
+                \              'must depend on plugin/frawor/functions)',
+                \     'urepl': 'invalid command description',
+            \}, '"Error while creating command %s for plugin %s: ".v:val'))
+endif
+"▶1 rewritefname    :: sid, Funcref → funcname
+function s:F.rewritefname(sid, Fref)
+    if type(a:Fref)==2
+        let fstr=string(a:Fref)[10:-3]
+    else
+        let fstr=a:Fref
+    endif
+    if fstr[:1] is# 's:'
+        let fstr='<SNR>'.a:sid.'_'.fstr[2:]
+    elseif fstr[:4] ==? '<SID>'
+        let fstr='<SNR>'.a:sid.'_'.fstr[5:]
+    endif
+    return fstr
+endfunction
+"▶1 wrapfunc     :: cmd, fname, fdescr → + :function
+function s:F.wrapfunc(cmd, fname, fdescr)
+    if type(a:fdescr)==type({})
+        let a:cmd.fs[a:fname[2:]]=call(a:cmd.wrapfunc, [a:fdescr], {})
+        let args='a:000'
+    else
+        let lcomp=len(a:fdescr)
+        if lcomp==1
+            if type(a:fdescr[0])!=type('')
+                call s:_f.throw('1nstr', a:cmd.id, a:cmd.plid)
+            endif
+            let compargs=['-onlystrings '.a:fdescr[0], 'complete']
+        elseif lcomp==2
+            let compargs=a:fdescr
+        else
+            call s:_f.throw('invclen', a:cmd.id, a:cmd.plid)
+        endif
+        let compargs+=[a:cmd.g]
+        unlet a:cmd.g
+        let [a:cmd.fs[a:fname[2:]], a:cmd.FWCid]=
+                    \call(s:_f.fwc.compile, compargs, {})
+        let args     = '[call(s:F.splitfunc, '.
+                    \        'a:000'.
+                    \        ((a:cmd.sp is 0)?
+                    \           (', '):
+                    \           ('+[s:commands.'.a:cmd.id.'.sp], ')).
+                    \        '{}).curargs]'
+    endif
+    execute      'function '.a:fname."(...)\n"
+                \'    return call(s:commands.'.a:cmd.id.'.fs.'.a:fname[2:].', '.
+                \                 args.", {})\n".
+                \'endfunction'
+endfunction
+"▶1 add_commands :: {f} → + p:_commands
+function s:F.add_commands(plugdict, fdict)
+    if !has_key(a:plugdict.g, '_commands') ||
+                \type(a:plugdict.g._commands)!=type([])
+        let a:plugdict.g._commands=[]
+    endif
+endfunction
+"▶1 delcommands  :: {f} + p:_commands → + :delcommand, p:_commands
+function s:F.delcommands(plugdict, fdict)
+    if !has_key(a:plugdict.g, '_commands') ||
+                \type(a:plugdict.g._commands)!=type([])
+        return
+    endif
+    for cmd in filter(copy(a:plugdict.g._commands),
+                \     'type(v:val)=='.type('').' && v:val=~#"\\v^\\u\\w+$" && '.
+                \     'exists(":".v:val)')
+        execute 'delcommand' cmd
+    endfor
+endfunction
+call s:_f.newfeature('delcommands', {'unloadpre': s:F.delcommands,
+            \                         'register': s:F.add_commands,
+            \                       'ignoredeps': 1})
+"▶1 splitfunc    :: arglead, cmdline, curpos[, cmdsplit] → cmddict
+" cmdsplit :: Fref | Regex
+"  cmddict :: {range: String, command: String, bang: Bool,
+"              curargs: [String], args: [String],
+"              arglead: String, position: UInt, cmdline: String}
+let s:argsplitregex='\(\\\@<!\(\\.\)*\\\)\@<! '
+let s:rangeregex='\m^\(%\|'.
+            \         '\('.
+            \           '\(\d\+\|'.
+            \             '[.$]\|'.
+            \             '''.\|'.
+            \             '\\[/?&]\|'.
+            \             '/\([^\\/]\|\\.\)\+/\=\|'.
+            \             '?\([^\\?]\|\\.\)\+?\='.
+            \           '\)'.
+            \           '\([+-]\d\+\)\='.
+            \           '[;,]\='.
+            \         '\)*'.
+            \       '\)\=\s*'
+function s:F.splitfunc(arglead, cmdline, curpos, ...)
+    let r={}
+    let r.arglead=a:arglead
+    let r.position=a:curpos
+    let r.cmdline=a:cmdline
+    let r.range=matchstr(a:cmdline, s:rangeregex)
+    let r.command=matchstr(a:cmdline[len(r.range):], '\v^(\u[[:alnum:]_]*)!?')
+    let d={}
+    let d.split=get(a:000, 0, s:argsplitregex)
+    if type(d.split)==type('')
+        let r.args=split(a:cmdline[(len(r.range)+len(r.command)):], d.split)
+        let r.curargs=split(a:cmdline[(len(r.range)+len(r.command)):(a:curpos)],
+                    \       d.split)
+        if empty(a:arglead)
+            call add(r.curargs, '')
+        endif
+    else
+        let r.args=d.split(a:cmdline[(len(r.range)+len(r.command)):])
+        let r.curargs=d.split(a:cmdline[len(r.range)+len(r.command):(a:curpos)])
+    endif
+    let r.bang=(r.command[-1:] is# '!')
+    return r
+endfunction
+"▶1 cmdsplit     :: cmdline[, cmdsplit] → [String]
+function s:F.cmdsplit(cmdline, ...)
+    let d={}
+    let d.split=get(a:000, 0, s:argsplitregex)
+    if type(d.split)==type('')
+        return split(a:cmdline, d.split)
+    else
+        return d.split(a:cmdline)
+    endif
+endfunction
+"▶1 command feature
+let s:F.command={}
+let s:commands={}
+let s:bufcommands={}
+let s:fts={}
+"▶2 addfunc      :: cmd, fdescr → + cmd | :function
+function s:F.addfunc(cmd, plstatus, fdescr)
+    if a:plstatus!=2
+        let fpattern='*'.(s:_sid).'_'.(a:cmd.compfname[2:])
+        let augname=s:compaugprefix.(a:cmd.id)
+        call s:_f.augroup.add(augname, [['FuncUndefined', fpattern, 0,
+                    \                   [s:F.loadplugin, a:cmd]]])
+        call add(a:cmd.augs, augname)
+        call add(a:cmd.funs, [a:cmd.compfname, a:fdescr])
+    else
+        call s:F.wrapfunc(a:cmd, a:cmd.compfname, a:fdescr)
+    endif
+endfunction
+"▶2 loadplugin   :: cmd → + FraworLoad(), :au!
+function s:F.loadplugin(cmd)
+    call FraworLoad(a:cmd.plid)
+    if !empty(a:cmd.augs)
+        call map(remove(a:cmd.augs, 0, -1), 's:_f.augroup.del(v:val)')
+    endif
+    if !empty(a:cmd.funs)
+        call map(remove(a:cmd.funs, 0, -1),
+                    \'s:F.wrapfunc(a:cmd, v:val[0], v:val[1])')
+        if empty(a:cmd.fs)
+            unlet a:cmd.fs
+        endif
+    endif
+endfunction
+"▶2 runcmd       :: cmd → + ?
+function s:F.runcmd(cmd, args)
+    if type(a:cmd.f)==type({})
+        call s:F.loadplugin(a:cmd)
+        let a:cmd.f=call(a:cmd.wrapfunc, [a:cmd.f], {})
+        execute 'delcommand' a:cmd.id
+        execute 'command' a:cmd.newcmdstring
+        let a:cmd.cmdstring=a:cmd.newcmdstring
+        unlet a:cmd.newcmdstring
+    endif
+    return call(a:cmd.f, a:args, {})
+endfunction
+"▶2 command.add  :: {f}, cid, cstr, copts → + :command, …
+"▶3 getspfunc    :: plid, cid, copts, spref → Maybe sp
+function s:F.getspfunc(plid, cid, copts, spref)
+    let key=a:spref.'splitfunc'
+    if !has_key(a:copts, key)
+        return 0
+    elseif type(a:copts[key])==type('')
+        try
+            call matchstr('', a:copts[key])
+        catch
+            call s:_f.throw('invsreg', a:cid, a:plid, a:copts[key], v:exception)
+        endtry
+    elseif !exists('*a:copts[key]') && a:copts[key] isnot 0
+        call s:_f.throw('invsp', a:cid, a:plid, a:spref)
+    endif
+    return a:copts[key]
+endfunction
+"▲3
+let s:cmddefaults={
+            \   'nargs': '0',
+            \'complete':  0,
+            \   'range':  0,
+            \   'count':  0,
+            \    'bang':  0,
+            \     'bar':  1,
+            \'register':  0,
+            \  'buffer':  0,
+        \}
+let s:compaugprefix='LoadCompFunc_'
+function s:F.command.add(plugdict, fdict, cid, cstr, copts)
+    "▶3 Checking arguments
+    if type(a:cid)!=type('')
+        call s:_f.throw('cidnstr', a:plugdict.id)
+    elseif a:cid!~#'^\u\w*$'
+        call s:_f.throw('invcid', a:plugdict.id, a:cid)
+    elseif has_key(s:commands, a:cid)
+        call s:_f.throw('ciddef', a:cid, a:plugdict.id, s:commands[a:cid].plid)
+    elseif exists(':'.a:cid)
+        call s:_f.throw('cidedef', a:cid, a:plugdict.id)
+    elseif type(a:copts)!=type({})
+        call s:_f.throw('coptsndct', a:cid, a:plugdict.id)
+    endif
+    "▲3
+    let cmd   =   {'id': a:cid,
+                \'plid': a:plugdict.id,
+                \'augs': [],
+                \'funs': [],
+                \  'fs': {},
+                \}
+    if a:plugdict.isftplugin
+        let cmd.filetype=matchstr(a:plugdict.id, '\v\/@<=[^/]+')
+    endif
+    let cmdstring=''
+    let addargs=[]
+    "▶3 Process *splitfunc
+    let cmd.sp=s:F.getspfunc(a:plugdict.id, a:cid, a:copts, '')
+    let cmd.rsp=s:F.getspfunc(a:plugdict.id, a:cid, a:copts, 'r')
+    "▶3 Create :command -options
+    for [key, value] in sort(items(s:cmddefaults))
+        if a:plugdict.isftplugin && key is# 'buffer'
+            let value=1
+        elseif has_key(a:copts, key)
+            "▶4 Completion
+            if key is# 'complete'
+                let d={}
+                let d.complete=a:copts.complete
+                let tcomplete=type(d.complete)
+                let cmd.compfname='s:Complete'.cmd.id
+                "▶5 Use function
+                if tcomplete==2
+                    let fname=s:F.rewritefname(a:plugdict.sid, a:copts.complete)
+                    if fname=~#'^\d'
+                        let cmd.compfunc=d.complete
+                        let intfname='s:commands.'.cmd.id.'.compfunc'
+                        execute      "function ".cmd.compfname."(...)\n".
+                                    \"    return call(".intfname.",a:000,{})\n".
+                                    \"endfunction"
+                    else
+                        let cmd.compfname=fname
+                    endif
+                    let cmdstring.='-complete=customlist,'.(cmd.compfname).' '
+                "▶5 Use function described by dictionary
+                elseif tcomplete==type({})
+                    if !exists('*a:plugdict.g._f.wrapfunc')
+                        call s:_f.throw('nowrapfunc', a:cid, a:plugdict.id)
+                    endif
+                    let cmd.wrapfunc=a:plugdict.g._f.wrapfunc
+                    call s:F.addfunc(cmd, a:plugdict.status, d.complete)
+                    let cmdstring.='-complete=customlist,'.(cmd.compfname).' '
+                "▶5 Use FWC string
+                elseif tcomplete==type([])
+                    let cmd.g=a:plugdict.g
+                    call s:F.addfunc(cmd, a:plugdict.status, d.complete)
+                    let cmdstring.='-complete=customlist,'.(cmd.compfname).' '
+                "▶5 Use something else
+                elseif tcomplete==type('')
+                    let cmdstring.='-complete='.(d.complete).' '
+                "▶5 Fail
+                else
+                    call s:_f.throw('ucomp', a:cid, a:plugdict.id)
+                endif
+            "▶4 Other options
+            else
+                unlet value
+                let value=a:copts[key]
+                if key is# 'range'
+                    if index([0, 1, '%'], value)==-1
+                        call s:_f.throw('invrange', a:cid, a:plugdict.id,
+                                    \               string(value))
+                    elseif has_key(a:copts, 'count')
+                        call s:_f.throw('hascount', a:cid, a:plugdict.id)
+                    elseif value isnot 0
+                        let addargs+=['<line1>', '<line2>']
+                    endif
+                elseif key is# 'count'
+                    if  !(value is 0 || value is 1 ||
+                            \(type(value)==type('') && value=~#'^\d\+$'))
+                        call s:_f.throw('invcount', a:cid, a:plugdict.id,
+                                    \               string(value))
+                    elseif value isnot 0
+                        let addargs+=['<count>']
+                    endif
+                elseif (key is# 'bang' || key is# 'bar' || key is# 'register' ||
+                            \key is# 'buffer')
+                    if !(value is 0 || value is 1)
+                        call s:_f.throw('invkey', a:cid, a:plugdict.id, key)
+                    elseif key is# 'bang' && value
+                        let addargs+=['<bang>0']
+                    elseif key is# 'register' && value
+                        let addargs+=['<q-reg>']
+                    endif
+                elseif key is# 'nargs' && index(['0', '1', '*', '?', '+'],
+                            \                   value)==-1
+                    call s:_f.throw('invnargs', a:cid, a:plugdict.id,
+                                \               string(value))
+                endif
+            endif
+            "▲4
+        endif
+        if key is# 'nargs' && cmd.rsp isnot 0
+            let cmdstring.='-nargs=1 '
+        elseif value is 1
+            let cmdstring.='-'.key.' '
+        elseif type(value)==type('')
+            let cmdstring.='-'.key.'='.value.' '
+        endif
+        unlet value
+    endfor
+    "▲3
+    let cmdstring.=cmd.id.' '
+    "▶3 Process replacement
+    if type(a:cstr)==type('')
+        let cmdstring.=(a:cstr)
+    else
+        if type(a:cstr)==type({})
+            if !exists('*a:plugdict.g._f.wrapfunc')
+                call s:_f.throw('nowrapfunc', a:cid, a:plugdict.id)
+            endif
+            let cmd.wrapfunc=a:plugdict.g._f.wrapfunc
+            let cmd.f=a:cstr
+        elseif exists('*a:cstr')
+            let cmd.f=a:cstr
+        else
+            call s:_f.throw('urepl', a:cid, a:plugdict.id)
+        endif
+        let args=''
+        if !empty(addargs)
+            let args='['.join(addargs, ', ').']+'
+        endif
+        if cmd.rsp is 0
+            let args.='[<f-args>]'
+        elseif type(cmd.rsp)==type('')
+            let args.='s:F.cmdsplit(<q-args>, s:commands.'.a:cid.'.rsp)'
+        else
+            let args.='s:commands.'.a:cid.'.rsp(<q-args>)'
+        endif
+        if type(a:cstr)==type({})
+            let cmd.newcmdstring=cmdstring.
+                        \     'call call(s:commands.'.a:cid.'.f, '.args.', {})'
+            let cmdstring.='call s:F.runcmd(s:commands.'.a:cid.', '.args.')'
+        else
+            let cmdstring.='call call(s:commands.'.a:cid.'.f, '.args.', {})'
+        endif
+    endif
+    "▲3
+    let cmd.cmdstring=cmdstring
+    let a:fdict[cmd.id]=cmd
+    "▶3 Add cmd to various global variables, execute :command
+    let s:commands[cmd.id]=cmd
+    if has_key(cmd, 'filetype')
+        if !has_key(s:fts, cmd.filetype)
+            let s:fts[cmd.filetype]={}
+        endif
+        let s:fts[cmd.filetype][cmd.id]=cmd
+        let buf=bufnr('%')
+        if !has_key(s:bufcommands, buf)
+            let s:bufcommands[buf]={}
+        endif
+        if index(split(&filetype, '\.'), cmd.filetype)!=-1
+            execute 'command' cmdstring
+            let s:bufcommands[buf][cmd.id]=cmd
+        endif
+    else
+        execute 'command' cmdstring
+    endif
+    "▲3
+endfunction
+"▶2 command.del  :: {f}[, cid] → + :delcommand, s:commands
+function s:F.command.del(plugdict, fdict, ...)
+    let todel=keys(a:fdict)
+    if a:0
+        "▶3 Проверка аргумента
+        if type(a:1)!=type('')
+            call s:_f.throw('dcidnstr', a:plugdict.id)
+        elseif !has_key(a:fdict, a:1)
+            call s:_f.throw('nowncid', a:plugdict.id, a:1)
+        endif
+        "▲3
+        let todel=[a:1]
+    endif
+    for cid in todel
+        if has_key(a:fdict[cid], 'FWCid')
+            call s:_f.fwc.del(a:fdict[cid].FWCid)
+        endif
+        if has_key(a:fdict[cid], 'fs')
+            for fname in keys(a:fdict[cid].fs)
+                execute 'delfunction s:'.fname
+            endfor
+        endif
+        unlet s:commands[cid]
+        unlet a:fdict[cid]
+        execute 'delcommand' cid
+    endfor
+endfunction
+"▶2 Register feature
+call s:_f.newfeature('command', {'cons': s:F.command,
+            \                  'unload': s:F.command.del,})
+"▶1 ftcommand     :: () → + :delcommand, :command, s:bufcommands
+function s:F.ftcommand()
+    let buf=expand('<abuf>')
+    if has_key(s:bufcommands, buf)
+        for cid in keys(s:bufcommands[buf])
+            execute 'delcommand' cid
+        endfor
+    else
+        let s:bufcommands[buf]={}
+    endif
+    for filetype in filter(split(&filetype, '\V.'), 'has_key(s:fts, v:val)')
+        for cmd in values(s:fts[filetype])
+            execute 'command' cmd.cmdstring
+            let s:bufcommands[buf][cmd.id]=cmd
+        endfor
+    endfor
+endfunction
+"▶1 bufentered    :: () → s:F.ftcommand()
+function s:F.bufentered()
+    let buf=expand('<abuf>')
+    if !has_key(s:bufcommands, buf)
+        call s:F.ftcommand()
+    endif
+endfunction
+"▶1 bufdeleted    :: () → + s:bufcommands
+function s:F.bufdeleted()
+    let buf=expand('<abuf>')
+    if has_key(s:bufcommands, buf)
+        unlet s:bufcommands[buf]
+    endif
+endfunction
+"▶1 Create autocommands
+call s:_f.augroup.add('Commands', [['BufEnter',  '*', 0, s:F.bufentered],
+            \                      ['BufDelete', '*', 0, s:F.bufdeleted],
+            \                      ['Filetype',  '*', 0, s:F.ftcommand ]])
+"▶1
+call frawor#Lockvar(s:, 'commands,fts,bufcommands')
+" vim: fmr=▶,▲ sw=4 ts=4 sts=4 et tw=80

autoload/frawor/decorators.vim

+"▶1 Header
+scriptencoding utf-8
+execute frawor#Setup('0.0', {'@/resources': '0.0'})
+"▶1 Define messages
+if v:lang=~?'ru'
+    let s:_messages={
+                \       'nodec': 'Обвязка «%s» не существует',
+                \    'deidnstr': 'Имя обвязки, создаваемой для дополнения %s, '.
+                \                'не является строкой',
+                \     'invdeid': 'Ошибка создания обвязки для дополнения %s: '.
+                \                'строка «%s» не может являться именем обвязки',
+            \}
+    call extend(s:_messages, map({
+                \     'deiddef': 'обвязка уже определена дополнением %s',
+                \     'decnfun': 'обвязка является не является ссылкой '.
+                \                'на функцию',
+                \      'deuref': 'обвязка является ссылкой '.
+                \                'на неизвестную функцию',
+            \},
+            \'"Ошибка создания обвязки %s для дополнения %s: ".v:val'))
+else
+    let s:_messages={
+                \       'nodec': 'Decorator `%s'' does not exist',
+                \    'deidnstr': 'Error while creating decorator '.
+                \                'for plugin %s: its identifier is not '.
+                \                'a String',
+                \     'invdeid': 'Error while creating decorator '.
+                \                'for plugin %s: String `%s'' is not a valid '.
+                \                'decorator identifier',
+            \}
+    call extend(s:_messages, map({
+                \     'deiddef': 'decorator already defined by plugin %s',
+                \     'decnfun': 'decorator is not a function reference',
+                \      'deuref': 'decorator is a reference to unknown function',
+            \},
+            \'"Error while creating decorator %s for plugin %s: ".v:val'))
+endif
+"▶1 rewritefname    :: sid, Funcref → funcname
+function s:F.rewritefname(sid, Fref)
+    let fstr=string(a:Fref)[10:-3]
+    if fstr[:1] is# 's:'
+        let fstr='<SNR>'.a:sid.'_'.fstr[2:]
+    endif
+    return fstr
+endfunction
+"▶1 refunction      :: sid, Funcref, throwargs → Funcref
+function s:F.refunction(sid, Fref, ...)
+    let fstr=s:F.rewritefname(a:sid, a:Fref)
+    if string(+fstr) is# fstr
+        return a:Fref
+    else
+        if !exists('*'.fstr)
+            call call(s:_f.throw, a:000, {})
+        endif
+        return function(fstr)
+    endif
+endfunction
+"▶1 adddecorator feature
+"▶2 adddecorator    :: {f}, deid, Decorator::Funcref → + fdict, s:decorators
+let s:decorators={}
+let s:lastdeid=0
+function s:F.adddecorator(plugdict, fdict, deid, Decorator)
+    "▶3 Check arguments
+    if type(a:deid)!=type('')
+        call s:_f.throw('deidnstr', a:plugdict.id)
+    elseif a:deid!~#'^\w\+$' && a:deid isnot# '_'
+        call s:_f.throw('invdeid', a:plugdict.id, a:deid)
+    elseif has_key(s:decorators, a:deid)
+        call s:_f.throw('deiddef', a:deid, a:plugdict.id,
+                    \              s:decorators[a:deid].plid)
+    elseif type(a:Decorator)!=2
+        call s:_f.throw('decnfun', a:deid, a:plugdict.id)
+    endif
+    "▲3
+    let d={}
+    let decorator={'id': a:deid,
+                \'plid': a:plugdict.id,
+                \'pref': printf('v%x_%s', s:lastdeid, a:deid),
+                \'func': s:F.refunction(a:plugdict.sid, a:Decorator,
+                \                       'deuref', a:deid, a:plugdict.id),}
+    let s:lastdeid+=1
+    let a:fdict[decorator.id]=decorator
+    let s:decorators[decorator.id]=decorator
+endfunction
+"▶2 deldecorators
+function s:F.deldecorators(plugdict, fdict)
+    for decorator in values(a:fdict)
+        unlet s:decorators[decorator.id]
+        unlet a:fdict[decorator.id]
+    endfor
+endfunction
+"▶2 Register feature
+call s:_f.newfeature('adddecorator', {'cons': s:F.adddecorator,
+            \                       'unload': s:F.deldecorators,})
+"▶1 getdecorator
+function s:F.getdecorator(id)
+    if !has_key(s:decorators, a:id)
+        call s:_f.throw('nodec', a:id)
+    endif
+    return s:decorators[a:id]
+endfunction
+call s:_f.postresource('getdecorator', s:F.getdecorator)
+"▶1
+call frawor#Lockvar(s:, 'decorators,lastdeid')
+" vim: fmr=▶,▲ sw=4 ts=4 sts=4 et tw=80

autoload/frawor/decorators/altervars.vim

+"▶1 Header
+scriptencoding utf-8
+execute frawor#Setup('0.1', {'@/decorators': '0.0',
+            \                    '@/checks': '0.0',
+            \      '@/decorators/altervars': '0.0'})
+"▶1 Define messages
+if v:lang=~?'ru'
+    let s:_messages={
+                \    'idnotstr': 'Ошибка создания сохраняющей и '.
+                \                'восстанавливающей функций для '.
+                \                'дополнения %s: их название '.
+                \                'не является строкой',
+                \       'invid': 'Ошибка создания сохраняющей и '.
+                \                'восстанавливающей функций для '.
+                \                'дополнения %s: строка «%s» '.
+                \                'не может являться названием',
+            \}
+    call extend(s:_messages, map({
+                \     'altnlst': 'ключ «altervars» не является списком',
+                \   'altelnlst': 'элемент %u значения ключа «altervars» '.
+                \                'описания функции не является списком',
+                \ 'altelinvlen': 'элемент %u значения ключа «altervars» '.
+                \                'описания функции имеет неверную длину %u '.
+                \                '(список должен содержать один или '.
+                \                 'два элемента)',
+                \   'altelnstr': 'первый элемент элемента %u значения ключа '.
+                \                '«altervars» описания функции '.
+                \                'не является строкой',
+                \    'invvnlen': 'неверен первый элемент элемента %u '.
+                \                'значения ключа «altervars»: длина строки '.
+                \                'не может быть меньше двух символов',
+                \    'invvname': 'неверен первый элемент элемента %u '.
+                \                'значения ключа «altervars»: строка «%s» '.
+                \                'не может являться именем переменной',
+                \    'noocpref': 'неверен первый элемент элемента %u '.
+                \                'значения ключа «altervars»: в имени '.
+                \                'настройки «%s» отсутстувет префикс '.
+                \                '(g: или l:)',
+                \    'invoname': 'неверен первый элемент элемента %u '.
+                \                'значения ключа «altervars»: строка «%s» '.
+                \                'не может являться именем настройки',
+                \     'invoval': 'неверен второй элемент элемента %u '.
+                \                'значения ключа «altervars»: '.
+                \                'тип элемента не совпадает с типом настройки',
+                \     'noclose': 'неверен первый элемент элемента %u '.
+                \                'значения ключа «altervars»: '.
+                \                'отсутствует закрывающая скобка',
+                \     'spnoarg': 'неверен первый элемент элемента %u '.
+                \                'значения ключа «altervars»: '.
+                \                'сохраняющая функция %s не принимает '.
+                \                'дополнительных аргументов',
+                \'acheckfailed': 'неверен первый элемент элемента %u '.
+                \                'значения ключа «altervars»: '.
+                \                'аргумент сохраняющей функции %s не прошёл '.
+                \                'проверку',
+                \      'usaver': 'неверен первый элемент элемента %u '.
+                \                'значения ключа «altervars»: '.
+                \                'неизвестна сохраняющая функция %s',
+                \    'spmisarg': 'неверен первый элемент элемента %u '.
+                \                'значения ключа «altervars»: '.
+                \                'сохраняющая функция %s требует наличия '.
+                \                'дополнительного аргумента',
+                \      'notdep': 'неверен первый элемент элемента %u '.
+                \                'значения ключа «altervars»: '.
+                \                'дополнение %s, определившее сохраняющую '.
+                \                'функцию %s, не указано в списке зависимостей',
+                \        'ualt': 'неверен первый элемент элемента %u '.
+                \                'значения ключа «altervars»: '.
+                \                'способ обработки «%s» неизвестен',
+            \}, '"Ошибка создания функции %s для дополнения %s: ".v:val'))
+    call extend(s:_messages, map({
+                \     'ssiddef': 'функции уже определены дополнением %s',
+                \  'savnotfunc': 'второй аргумент не является '.
+                \                'ссылкой на функцию',
+                \  'setnotfunc': 'третий аргумент не является '.
+                \                'ссылкой на функцию',
+                \  'ssmanyargs': 'слишком большое количество аргументов',
+                \  'ssoptsndct': 'дополнительный аргумент не является словарём',
+                \   'sssavuref': 'сохраняющая функция является ссылкой '.
+                \                'на неизвестную функцию',
+                \   'ssseturef': 'восстанавливающая функция является ссылкой '.
+                \                'на неизвестную функцию',
+            \},
+            \'"Ошибка создания сохраняющей и восстанавливающей функций '.
+            \ '%s для дополнения %s: ".v:val'))
+else
+    let s:_messages={
+                \    'idnotstr': 'Error while creating saver and setter '.
+                \                'functions for plugin %s: id is not a String',
+                \       'invid': 'Error while creating saver and setter '.
+                \                'functions for plugin %s: string `%s'' '.
+                \                'is not a valid identifier',
+            \}
+    call extend(s:_messages, map({
+                \     'altnlst': 'key `altervars'' is not a list',
+                \   'altelnlst': 'element %u of `altervars'' value '.
+                \                'of the function description is not a list',
+                \ 'altelinvlen': 'element %u of `altervars'' value '.
+                \                'of the function description has invalid '.
+                \                'length %u while expected 1 or 2',
+                \   'altelnstr': 'first element of element %u of `altervars'' '.
+                \                'value of the function description is '.
+                \                'not a string',
+                \    'invvnlen': 'first element of element %u of `altervars'' '.
+                \                'value is not valid: string must be at least '.
+                \                'two characters long',
+                \    'invvname': 'first element of element %u of `altervars'' '.
+                \                'value is not valid: string `%s'' '.
+                \                'is not a variable name',
+                \    'noocpref': 'first element of element %u of `altervars'' '.
+                \                'value is not valid: there should be '.
+                \                'g: or l: prefix before option name in '.
+                \                'the string `%s''',
+                \    'invoname': 'first element of element %u of `altervars'' '.
+                \                'value is not valid: string `%s'' '.
+                \                'is not an option name',
+                \     'invoval': 'second element of element %u '.
+                \                'of `altervars'' value is not valid: '.
+                \                'type of the element does not match type of '.
+                \                'the option',
+                \     'noclose': 'first element of element %u of `altervars'' '.
+                \                'value is not valid: closing bracket '.
+                \                'not found',
+                \     'spnoarg': 'first element of element %u of `altervars'' '.
+                \                'value is not valid: saver %s does '.
+                \                'not accept additional arguments',
+                \'acheckfailed': 'first element of element %u of `altervars'' '.
+                \                'value is not valid: argument of saver %s '.
+                \                'failed to pass check',
+                \      'usaver': 'first element of element %u of `altervars'' '.
+                \                'value is not valid: saver %s is not known',
+                \    'spmisarg': 'first element of element %u of `altervars'' '.
+                \                'value is not valid: saver %s requires '.
+                \                'additional argument',
+                \      'notdep': 'first element of element %u of `altervars'' '.
+                \                'value is not valid: plugin %s that defined '.
+                \                'saver %s is not in dependencies list',
+                \        'ualt': 'first element of element %u of `altervars'' '.
+                \                'value is not valid: unable to determine '.
+                \                'how to process %s',
+            \}, '"Error while creating function %s for plugin %s: ".v:val'))
+    call extend(s:_messages, map({
+                \     'ssiddef': 'they were already defined by plugin %s',
+                \  'savnotfunc': 'saver is not a function reference',
+                \  'setnotfunc': 'setter is not a function reference',
+                \  'ssmanyargs': 'too many arguments',
+                \  'ssoptsndct': 'options argument is not a Dictionary',
+                \   'sssavuref': 'saver is a reference to unknown function',
+                \   'ssseturef': 'setter is a reference to unknown function',
+            \},
+            \'"Error while creating saver and setter functions with id %s '.
+            \ 'for plugin %s: ".v:val'))
+endif
+"▶1 rewritefname    :: sid, Funcref → funcname
+function s:F.rewritefname(sid, Fref)
+    let fstr=string(a:Fref)[10:-3]
+    if fstr[:1] is# 's:'
+        let fstr='<SNR>'.a:sid.'_'.fstr[2:]
+    endif
+    return fstr
+endfunction
+"▶1 refunction      :: sid, Funcref, throwargs → Funcref
+function s:F.refunction(sid, Fref, ...)
+    let fstr=s:F.rewritefname(a:sid, a:Fref)
+    if string(+fstr) is# fstr
+        return a:Fref
+    else
+        if !exists('*'.fstr)
+            call call(s:_f.throw, a:000, {})
+        endif
+        return function(fstr)
+    endif
+endfunction
+"▶1 Decorator
+let s:altervars={'lastid': 0}
+let s:ss={}
+let s:altdict={'altervars': s:altervars,
+            \         'ss': s:ss}
+function s:F.altervars(plugdict, fname, arg)
+    "▶2 Check a:arg
+    if type(a:arg)!=type([])
+        call s:_f.throw('altnlst', a:fname, a:plugdict.id)
+    endif
+    "▶2 Define variables
+    let preret=['let @$@={}',
+                \     'try',]
+    let postret=['finally']
+    let laltvars=len(a:arg)
+    let plid=a:plugdict.id
+    let i=0
+    let id=printf('%x', s:altervars.lastid)
+    let fpref='@%@.altervars.'.id
+    let altcopy=map(copy(a:arg), 'copy(v:val)')
+    let s:altervars[id]=altcopy
+    let s:altervars.lastid+=1
+    "▲2
+    while i<laltvars
+        "▶2 Check current element
+        if type(a:arg[i])!=type([])
+            call s:_f.throw('altelnlst', a:fname, plid, i)
+        elseif empty(a:arg[i]) || len(a:arg[i])>2
+            call s:_f.throw('altelinvlen', a:fname, plid, i, len(a:arg[i]))
+        elseif type(a:arg[i][0])!=type('')
+            call s:_f.throw('altelnstr', a:fname, plid, i)
+        endif
+        "▲2
+        let element=altcopy[i]
+        let varname=element[0]
+        let vnlen=len(varname)
+        let hasvar=(len(element)>1)
+        let warnargs="'".varname."', '".a:fname."', ".
+                    \substitute(string(plid), "\n",
+                    \           '''."\\n".''', 'g').', v:exception'
+        "▶2 Check varname length
+        if vnlen<2
+            call s:_f.throw('invvnlen', a:fname, plid, i)
+        "▶2 Process variables
+        elseif varname[1] is# ':' && index(['g','b','t','w'], varname[0])!=-1
+            "▶3 Check variable name
+            if varname[2:]!~#'^\h\w*$'
+                call s:_f.throw('invvname', a:fname, plid, i, varname[2:])
+            endif
+            "▲3
+            let preret+=['if exists("'.varname.'")',
+                        \          'let @$@.'.i.'='.varname]
+            if hasvar
+                " Unlet variable to be sure that it won't cause E706 
+                " (variable type mismatch) error
+                let preret+=['unlet '.varname,
+                            \   'endif',
+                            \   'let '.varname.'=']
+                if type(element[1])==type(0) ||
+                            \(has('float') && type(element[1])==type(0.0) &&
+                            \ string(element[1])!~'\w')
+                    let preret[-1].=string(element[1])
+                else
+                    let preret[-1].=fpref.'['.i.'][1]'
+                endif
+            else
+                call add(preret, 'endif')
+            endif
+            " Restore the previous status: variable will be left undefined if it 
+            " was not defined before function was run
+            let postret+=['if exists("'.varname.'")',
+                        \         'unlet '.varname,
+                        \     'endif',
+                        \     'if has_key(@$@, '.i.')',
+                        \         'let '.varname.'=@$@.'.i,
+                        \     'endif',]
+        "▶2 Process options
+        elseif varname[0] is# '&' && vnlen>3
+            "▶3 Check option name
+            if varname[2] isnot# ':' || !(varname[1] is# 'g' || varname[1] is# 'l')
+                call s:_f.throw('noocpref', a:fname, plid, i, varname)
+            elseif varname[3:]!~#'^\l\+$'
+                call s:_f.throw('invoname', a:fname, plid, i, varname[3:])
+            endif
+            "▲3
+            if exists('+'.varname[1:])
+                let ovarname='@$@'.varname[1].'_'.varname[3:]
+                call add(preret, 'let '.ovarname.'='.varname)
+                if hasvar
+                    "▶3 Check value type
+                    if type(element[1])!=type(eval(varname))
+                        call s:_f.throw('invoval', a:fname, plid, i)
+                    endif
+                    "▲3
+                    call add(preret, 'let '.varname.'='.
+                                \substitute(string(element[1]), '\n',
+                                \                  '''."\\n".''', 'g'))
+                endif
+                let postret+=['let '.varname.'='.ovarname]
+            endif
+        "▶2 Process special
+        elseif varname[0] is# '+'
+            "▶3 *args, varname
+            let saveargs=''
+            let setargs=fpref.'['.i.'][1]'
+            let set2args='@$@.'.i
+            let varname=varname[1:]
+            "▲3
+            let bidx=stridx(varname, '(')
+            "▶3 If argument is supplied
+            if bidx!=-1
+                "▶4 Altering varname, element and *args variables
+                let noclose=(varname[-1:] isnot# ')')
+                if !hasvar
+                    call add(element, 0)
+                endif
+                call add(element, varname[(bidx+1):-2])
+                let varname=varname[:(bidx-1)]
+                let element[0]='+'.varname
+                let saveargs=fpref.'['.i.'][2]'
+                let setargs.=', '.saveargs
+                let set2args.=', '.saveargs
+                "▶4 Check existance of special
+                if !has_key(s:ss, varname)
+                    call s:_f.throw('usaver', a:fname, plid, i, varname)
+                endif
+                let ssdef=s:ss[varname]
+                "▶4 Checking () expr
+                if noclose
+                    call s:_f.throw('noclose', a:fname, plid, i)
+                elseif !ssdef.hasarg
+                    call s:_f.throw('spnoarg', a:fname, plid, i, varname)
+                elseif has_key(ssdef, 'checker') && !ssdef.checker([element[2]])
+                    call s:_f.throw('acheckfailed', a:fname, plid, i, varname)
+                endif
+            "▶3 Else just check existance of special
+            else
+                if !has_key(s:ss, varname)
+                    call s:_f.throw('usaver', a:fname, plid, i, varname)
+                endif
+            endif
+            "▲3
+            let ssdef=s:ss[varname]
+            "▶3 Check special name and existance of all arguments
+            if ssdef.hasarg==2 && bidx==-1
+                call s:_f.throw('spmisarg', a:fname, plid, i, varname)
+            elseif !has_key(a:plugdict.dependencies, ssdef.plid)
+                call s:_f.throw('notdep', a:fname, plid, i, ssdef.plid, varname)
+            endif
+            "▲3
+            let preret+=['try',
+                        \          'let @$@.'.i.'=@%@.ss.'.varname.'.saver'.
+                        \                                      '('.saveargs.')',
+                        \      'catch',
+                        \          'call s:_f.warn("savexcept", '.warnargs.')',
+                        \      'endtry',]
+            if hasvar
+                let preret+=['try',
+                            \          'call @%@.ss.'.varname.'.setter'.
+                            \                                   '('.setargs.')',
+                            \      'catch',
+                            \          'call s:_f.warn("setexcept", '.
+                            \                                 warnargs.')',
+                            \      'endtry',]
+            endif
+            let postret+=['try',
+                        \         'call @%@.ss.'.varname.'.setter'.
+                        \                                      '('.set2args.')',
+                        \     'catch',
+                        \         'call s:_f.warn("set2except", '.warnargs.')',
+                        \     'endtry',]
+        "▶2 Process unknown
+        else
+            call s:_f.throw('ualt', a:fname, plid, i, varname)
+        endif
+        "▶2 Finish cycle (increment+unlet)
+        let i+=1
+        unlet element
+        "▲2
+    endwhile
+    call add(postret, 'endtry')
+    return [192, '@@@', s:altdict, preret, postret, 0]
+endfunction
+call s:_f.adddecorator('altervars', s:F.altervars)
+"▶1 addaltspecial feature
+"▶2 delaltspecials  :: {f} → s:ss, fdict
+function s:F.delaltspecials(plugdict, fdict)
+    for ssdef in values(a:fdict)
+        unlet s:ss[ssdef.id]
+        unlet a:fdict[ssdef.id]
+    endfor
+endfunction
+"▶2 addaltspecial   :: {f}, ssid, Saver::Funcref, Setter::Funcref[, ssopts] → +…
+function s:F.addaltspecial(plugdict, fdict, ssid, Saver, Setter, ...)
+    "▶3 Check arguments
+    if type(a:ssid)!=type('')
+        call s:_f.throw('idnotstr', a:plugdict.id)
+    elseif a:ssid!~#'^\h\w*$'
+        call s:_f.throw('invid', a:plugdict.id, a:ssid)
+    elseif has_key(s:ss, a:ssid)
+        call s:_f.throw('ssiddef', a:ssid, a:plugdict.id,
+                    \              s:ss[a:ssid].plid)
+    elseif type(a:Saver)!=2
+        call s:_f.throw('savnotfunc', a:ssid, a:plugdict.id)
+    elseif type(a:Setter)!=2
+        call s:_f.throw('setnotfunc', a:ssid, a:plugdict.id)
+    elseif a:0>1
+        call s:_f.throw('ssmanyargs', a:ssid, a:plugdict.id)
+    elseif a:0
+        if type(a:1)!=type({})
+            call s:_f.throw('ssoptsndct', a:ssid, a:plugdict.id)
+        endif
+    endif
+    "▲3
+    let ssdef={
+                \    'id': a:ssid,
+                \  'plid': a:plugdict.id,
+                \ 'saver': s:F.refunction(a:plugdict.sid, a:Saver,
+                \                         'sssavuref', a:ssid, a:plugdict.id),
+                \'setter': s:F.refunction(a:plugdict.sid, a:Setter,
+                \                         'ssseturef', a:ssid, a:plugdict.id),
+                \'hasarg': 0,
+            \}
+    if a:0
+        if has_key(a:1, 'requiresarg')
+            let ssdef.hasarg=2
+        elseif has_key(a:1, 'acceptsarg')
+            let ssdef.hasarg=1
+        endif
+        if has_key(a:1, 'checker')
+            let ssdef.checker=s:_f.conschecker(a:1.checker)
+        endif
+    endif
+    let s:ss[ssdef.id]=ssdef
+    let a:fdict[ssdef.id]=ssdef
+endfunction
+"▶2 Register feature
+call s:_f.newfeature('addaltspecial', {'cons': s:F.addaltspecial,
+            \                        'unload': s:F.delaltspecials,})
+"▶1 Savers/setters
+"▶2 Define dictionaries
+let s:F.saver={}
+let s:F.setter={}
+let s:F.sschk={}
+"▶2 window
+function s:F.saver.window()
+    return [tabpagenr(), winnr()]
+endfunction
+function s:F.setter.window(twnr)
+    if type(a:twnr)==type([]) && len(a:twnr)==2
+                \&& type(a:twnr[0])==type(0) && a:twnr[0]<=tabpagenr('$')
+                \&& type(a:twnr[1])==type(0) && a:twnr[1]<=winnr('$')
+        try
+            if tabpagenr()!=a:twnr[0]
+                execute 'tabnext' a:twnr[0]
+            endif
+            if winnr()!=a:twnr[1]
+                execute a:twnr[1].'wincmd w'
+            endif
+        catch
+            echohl ErrorMsg
+            echomsg v:exception
+            echohl None
+        endtry
+    endif
+endfunction
+call s:_f.addaltspecial('window', s:F.saver.window, s:F.setter.window)
+"▶2 winview
+call s:_f.addaltspecial('winview', function('winsaveview'),
+            \                      function('winrestview'))
+"▶2 folds
+function s:F.saver.folds()
+    let r=[]
+    for line in range(1, line('$'))
+        if foldclosed(line)!=-1
+            execute line.'foldopen'
+            call insert(r, line)
+        endif
+    endfor
+    return r
+endfunction
+function s:F.setter.folds(flist)
+    if type(a:flist)==type([]) &&
+                \empty(filter(copy(a:flist), 'type(v:val)!='.type(0)))
+        try
+            normal! zR
+            for line in a:flist
+                execute line.'foldclose'
+            endfor
+        catch
+            echohl ErrorMsg
+            echomsg v:exception
+            echohl None
+        endtry
+    endif
+endfunction
+call s:_f.addaltspecial('folds', s:F.saver.folds, s:F.setter.folds)
+"▶2 buffer
+function s:F.saver.buffer()
+    return bufnr('%')
+endfunction
+function s:F.setter.buffer(bufnr)
+    if type(a:bufnr)==type(0) && a:bufnr!=bufnr('%') && bufexists(a:bufnr)
+        try
+            execute 'buffer' a:bufnr
+        catch
+            " Setter must not throw anything: it may break things
+            echohl ErrorMsg
+            echomsg v:exception
+            echohl None
+        endtry
+    endif
+endfunction
+call s:_f.addaltspecial('buffer', s:F.saver.buffer, s:F.setter.buffer)
+"▶2 variables
+"▶3 check
+function s:F.sschk.variables(arg)
+    if a:arg[0]!~#'^[gtbw]$'
+        call s:_f.throw('invvcvar', a:arg[0])
+    endif
+    return 1
+endfunction
+"▶3 save
+function s:F.saver.variables(...)
+    let dname=get(a:000, 0, 'g').':'
+    return copy(eval(dname))
+endfunction
+"▶3 set
+function s:F.setter.variables(oldv, ...)
+    if type(a:oldv)!=type({})
+        return
+    endif
+    let dname=get(a:000, 0, 'g').':'
+    let v=eval(dname)
+    for name in filter(keys(v), '!has_key(a:oldv, v:val)')
+        unlet v[name]
+    endfor
+    call extend(v, filter(a:oldv, 'v:key=~#"^\\h\\w*$"'), 'force')
+endfunction
+"▲3
+call s:_f.addaltspecial('variables', s:F.saver.variables, s:F.setter.variables,
+            \                               {'checker': s:F.sschk.variables,
+            \                             'acceptsarg': 1,})
+"▶2 matches
+call s:_f.addaltspecial('matches',function('getmatches'),function('setmatches'))
+"▶2 qflist
+function s:F.setqflist(list)
+    return setqflist(a:list, 'r')
+endfunction
+call s:_f.addaltspecial('qflist', function('getqflist'), s:F.setqflist)
+"▲2
+unlet s:F.saver s:F.setter s:F.sschk
+"▶1
+call frawor#Lockvar(s:, 'altervars,ss,altdict')
+lockvar 1 s:altdict
+" vim: fmr=▶,▲ sw=4 ts=4 sts=4 et tw=80

autoload/frawor/functions.vim

+"▶1 Header
+scriptencoding utf-8
+execute frawor#Setup('0.0', {'@/decorators': '0.0',
+            \              '@/autocommands': '0.0',})
+"▶1 _messages
+if v:lang=~?'ru'
+    let s:_messages={
+                \    'fnotdict': 'аргумент, описывающий функции, '.
+                \                'должен быть словарём',
+                \    'invfname': 'строка «%s» не может являться именем функции',
+                \        'uref': 'ключ «%s» описания фунцкции %s содержит '.
+                \                'ссылку на неизвестную функцию',
+            \}
+    call map(s:_messages, '"Ошибка создания функций для дополнения %s: ".'.
+                \           'v:val')
+    call extend(s:_messages, map({
+                \'foptsnotdict': 'описание функции не является словарём',
+                \        'fdef': 'функция уже определена',
+                \      'nofunc': 'описание функции не содержит '.
+                \                'ключа «function»',
+                \        'nref': 'ключ «%s» не является ссылкой на функцию',
+                \   'invdecret': 'декоратор %s вернул неверное значение',
+                \     'decndep': 'дополнение, определившее декоратор %s, '.
+                \                'не находится в списке зависимостей',
+            \},
+            \'"Ошибка создания функции %s для дополнения %s: ".v:val'))
+    call extend(s:_messages, {
+                \ 'checkfailed': 'Аргументы функции %s дополнения %s '.
+                \                'не прошли проверку',
+                \'filterfailed': 'Фильтр функции %s дополнения %s вернул '.
+                \                'значение, не являющееся списком',
+                \    'invvcvar': 'Строка «%s» не может указывать на то, '.
+                \                'какие переменные следует сохранить',
+                \   'savexcept': 'Ошибка при запуске сохраняющей функции %s '.
+                \                'для функции %s дополнения %s: %s',
+                \   'setexcept': 'Ошибка при запуске восстанавливающей '.
+                \                'функции %s для функции %s дополнения %s: %s',
+                \  'set2except': 'Ошибка при восстановлении значения функцией '.
+                \                '%s для функции %s дополнения %s: %s',
+                \     'oexcept': 'Ошибка при установке настройки %s '.
+                \                'для функции %s дополнения %s: %s',
+                \    'deceqpri': 'Приоритет декораторов %s и %s совпадает',
+            \})
+else
+    let s:_messages={
+                \    'fnotdict': 'functions argument must be a dictionary',
+                \    'invfname': '%s is not a valid function name',
+                \        'uref': 'key `%s'' of %s function description '.
+                \                'provides a reference to unknown function',
+            \}
+    call map(s:_messages, '"Error while creating functions for plugin %s: ".'.
+                \           'v:val')
+    call extend(s:_messages, map({
+                \'foptsnotdict': 'function description must be a Dictionary',
+                \        'fdef': 'function was already defined',
+                \      'nofunc': 'function description lacks '.
+                \                '`function'' key',
+                \        'nref': 'key `%s'' is not a function reference',
+                \   'invdecret': 'decorator %s returned invalid value',
+                \     'decndep': 'plugin that defined decorator %s '.
+                \                'is not in dependency list',
+            \},
+            \'"Error while creating function %s for plugin %s: ".v:val'))
+    call extend(s:_messages, {
+                \ 'checkfailed': 'Arguments of function %s of plugin %s '.
+                \                'failed to pass check',
+                \'filterfailed': 'Filter of function %s of plugin %s '.
+                \                'returned value that is not a list',
+                \    'invvcvar': 'String `%s'' does not describe which '.
+                \                'variables should be saved',
+                \   'savexcept': 'Error while running saver function %s '.
+                \                'for function %s of a plugin %s: %s',
+                \   'setexcept': 'Error while running setter function %s '.
+                \                'for function %s of a plugin %s: %s',
+                \  'set2except': 'Error while restoring value using setter '.
+                \                'function %s for function %s of a plugin %s: '.
+                \                '%s',
+                \     'oexcept': 'Error while setting option %s '.
+                \                'for function %s of a plugin %s: %s',
+                \    'deceqpri': 'Decorators %s and %s have equal priority',
+            \})
+endif
+"▶1 rewritefname    :: sid, Funcref → funcname
+function s:F.rewritefname(sid, Fref)
+    if type(a:Fref)==2
+        let fstr=string(a:Fref)[10:-3]
+    else
+        let fstr=a:Fref
+    endif
+    if fstr[:1] is# 's:'
+        let fstr='<SNR>'.a:sid.'_'.fstr[2:]
+    elseif fstr[:4] ==? '<SID>'
+        let fstr='<SNR>'.a:sid.'_'.fstr[5:]
+    endif
+    return fstr
+endfunction
+"▶1 refunction      :: sid, Funcref, throwargs → Funcref
+function s:F.refunction(sid, Fref, ...)
+    let fstr=s:F.rewritefname(a:sid, a:Fref)
+    if string(+fstr) is# fstr
+        return a:Fref
+    else
+        if !exists('*'.fstr)
+            call call(s:_f.throw, a:000, {})
+        endif
+        return function(fstr)
+    endif
+endfunction
+"▶1 delfunction     :: sid, Funcref → + :delfunction
+function s:F.delfunction(sid, Fref)
+    let fstr=s:F.rewritefname(a:sid, a:Fref)
+    if string(+fstr) is# fstr || fstr=~#'^[a-z_]\+$'
+        return 3
+    elseif !exists('*'.fstr)
+        return 2
+    endif
+    try
+        execute 'delfunction '.fstr
+    catch /^Vim(delfunction):E131:/
+        " Normally you should catch this error for FraworUnload function, so 
+        " it has bang