Commits

ZyX_I  committed 9113ac2

Added `addextfunctions' feature (untested)

  • Participants
  • Parent commits 564fdd7

Comments (0)

Files changed (2)

File doc/frawor.txt

 ==============================================================================
 4. Features                                                  *frawor-features*
 
-warn : function ({msgid}, ...) → {message}                     *frawor-f-warn*
+warn : function ({msgid}, ...) + s:g._messages → {message}     *frawor-f-warn*
         Echoes message `Frawor:{plid}:{message}' where {message} is obtained by 
         getting {msgid} out of `s:g._messages' (s:g is a dictionary that is 
         passed as last but one argument to FraworRegister). If more then one 
                    2. If feature definer was loaded after plugin that requires 
                       it, then it will be called just after feature definer is 
                       loaded.
-        init       Any value. Deep copy (|deepcopy()|) of it will be assigned to 
-                   plugdict.features[{fid}]. Note that if `load' key is present, 
-                   then this key will be ignored.
+        init       Any value. If `load' key is not present, then deep copy 
+                   (|deepcopy()|) of this value be assigned to 
+                   plugdict.features[{fid}]. Copying is done just before 
+                   assignement, so if you record a pointer to a |List| or 
+                   |Dictionary| that is a value of `init' key somewhere, 
+                   modifications done to these structures will take effect on 
+                   init value recorded in the next plugin.
         unload     Reference to a function that will take |frawor-t-plugdict| as
                    its first argument. This function will be called only once 
                    for each plugin that depends on the plugin that registers 
                    plugin which defined this feature in dependencies (feature 
                    definer). Key `cons' will be ignored for plugins that do not 
                    specify feature definer in dependencies.
-delfunctions : unloadpre                               *frawor-f-delfunctions*
+delfunctions : unloadpre + s:F._functions              *frawor-f-delfunctions*
         Defined in plugin/frawor/functions plugin. Deletes all non-anonymous 
         functions in s:F._functions dictionary before plugin is unloaded. Note 
         that you should not unload plugin/frawor/functions because it is 
         s:F._functions['s:Func']=function('s:Func')' after each plugin-local 
         function definition). This feature will throw an error if function is 
         already defined and there is no way to avoid that, so do not create 
-        functions that can unload your plugin with this feature.
+        functions that can unload your plugin with this feature. Each 
+        {funcdescr} must be a dictionary with the following keys:
+        Key        Description ~
+        function   Function reference. Determines function that will be called.
+        checker    If `checker' value is a function reference, then each time 
+                   wrapper function is called its arguments will be passed as 
+                   a single list to a checker. Checker then must return either 
+                   one indicating that check passed or zero indicating that 
+                   `checkfailed' exception must be thrown.
+        filter     Like `checker', but it must return either list (it will be 
+                   passed to a `function' then) or 0 (throws `filterfailed' 
+                   exception). If both `checker' and `filter' are present, then 
+                   `checker' is used first.
+        altervars  List of lists [({varname}, {value})]. If this key is present 
+                   before `function' is called all variables with given names 
+                   will be assigned corresponding {value}s and their values will 
+                   be restored after function returns (see |:finally|). 
+                   {varname} may be the following: `g:{gvarname}' for global 
+                   variables, buffer-local `b:{bvarname}', tabpage-local 
+                   `t:{tvarname}' and window-local `w:{wvarname}' variables as 
+                   well as `&g:{optname}' and `&l:{optname}' options will be 
+                   restored for buffer, tab or window that will be active after 
+                   function returns (regardless whether they were changed). 
+                   |v:count| and |v:register| are also accepted, they are 
+                   restored by prepending count or register to a command that 
+                   does nothing. `&{optname}' options are forbidden, use `&g:' 
+                   or `&l:' instead. In addition to options and variables you 
+                   can use `+{intname}', see |frawor-f-addalt|. Variables are 
+                   altered before `checker' or `filter' are called. Note: you 
+                   may omit {value} item. In this case variable is saved and 
+                   restored but not altered.
+                   Each list in a list that is passed as `altervars' option is 
+                   copied, but no |deepcopy()| is performed, so you may alter 
+                   variable {value}s if they are |Lists| or |Dictionaries|.
 
 ==============================================================================
 5. Type definitions                                             *frawor-types*

File plugin/frawor/functions.vim

 "▶1 _messages
 if v:lang=~?'ru'
     let s:g._messages={
-                \'fnotdict': 'Ошибка создания функций для дополнения %s: '.
-                \            'аргумент, описывающий функции, '.
-                \            'должен быть словарём',
+                \    'fnotdict': 'аргумент, описывающий функции, '.
+                \                'должен быть словарём',
+                \    'invfname': 'строка «%s» не может являться именем функции',
+                \        'uref': 'ключ «%s» описания фунцкции %s содержит '.
+                \                'ссылку на неизвестную функцию',
             \}
+    call map(s:g._messages, '"Ошибка создания функций для дополнения %s: ".'.
+                \           'v:val')
+    call extend(s:g._messages, map({
+                \'foptsnotdict': 'описание функции не является словарём',
+                \        'fdef': 'функция уже определена в дополнении %s',
+                \      'nofunc': 'описание функции не содержит '.
+                \                'ключа «functions»',
+                \        'nref': 'ключ «%s» не является ссылкой на функцию',
+                \     'altnlst': 'ключ «altervars» не является списком',
+                \   'altelnlst': 'элемент %u значения ключа «altervars» '.
+                \                'описания функции не является списком',
+                \ 'altelinvlen': 'элемент %u значения ключа «altervars» '.
+                \                'описания функции имеет неверную длину %u '.
+                \                '(список должен содержать один или '.
+                \                 'два элемента)',
+                \     'altnstr': 'первый элемент элемента %u значения ключа '.
+                \                '«altervars» описания функции '.
+                \                'не является строкой',
+                \    'invvname': 'неверен первый элемент элемента %u '.
+                \                'значения ключа «altervars»: строка «%s» '.
+                \                'не может являться именем переменной',
+                \    'noocpref': 'неверен первый элемент элемента %u '.
+                \                'значения ключа «altervars»: в имени '.
+                \                'настройки «%s» отсутстувет префикс '.
+                \                '(g: или l:)',
+                \    'invoname': 'неверен первый элемент элемента %u '.
+                \                'значения ключа «altervars»: строка «%s» '.
+                \                'не может являться именем настройки',
+                \     'invoval': 'неверен второй элемент элемента %u '.
+                \                'значения ключа «altervars»: '.
+                \                'значением настройки может быть только '.
+                \                'строка или число',
+                \      'usaver': 'неверен первый элемент элемента %u '.
+                \                'значения ключа «altervars»: '.
+                \                'неизвестна сохраняющая функция %s',
+                \      'usaver': 'неверен первый элемент элемента %u '.
+                \                'значения ключа «altervars»: '.
+                \                'способ обработки «%s» неизвестен',
+            \},
+            \'"Ошибка создания функции %s для дополнения %s: ".v:val'))
+    call extend(s:g._messages, {
+                \ 'checkfailed': 'Аргументы функции %s дополнения %s '.
+                \                'не прошли проверку',
+                \'filterfailed': 'Фильтр функции %s дополнения %s вернул '.
+                \                'значение, не являющееся списком',
+            \})
 else
     let s:g._messages={
-                \'fnotdict': 'Error while creating functions for %s: '.
-                \            'functions argument must be a dictionary',
+                \    '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:g._messages, '"Error while creating functions for plugin %s: ".'.
+                \           'v:val')
+    call extend(s:g._messages, map({
+                \'foptsnotdict': 'function description must be a Dictionary',
+                \        'fdef': 'function was already defined by plugin %s',
+                \      'nofunc': 'function description lacks '.
+                \                '`function'' key',
+                \        'nref': 'key `%s'' is not a function reference',
+                \     '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',
+                \     'altnstr': 'first element of element %u of `altervars'' '.
+                \                'value of the function description is '.
+                \                'not a string',
+                \    '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: '.
+                \                'can only assign strings or numbers to '.
+                \                'an option',
+                \      'usaver': 'first element of element %u of `altervars'' '.
+                \                'value is not valid: saver %s is not known',
+                \        '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:g._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',
+            \})
 endif
+"▶1 rewritefname
+function s:F.rewritefname(sid, Fref)
+    let fstr=string(a:Fref)[10:-3]
+    if fstr[:1]==#'s:'
+        let fstr=printf(s:g.sidfpref, a:sid).fstr[2:]
+    endif
+    return fstr
+endfunction
 "▶1 delfunctions
 function s:F.delfunctions(plugdict)
     let d={}
         if type(d.Function)!=2
             continue
         endif
-        let fstr=string(d.Function)[10:-3]
-        if fstr[:1]==#'s:'
-            let fstr=printf(s:g.sidfpref, a:plugdict.sid).fstr[2:]
-        elseif string(+fstr)==#fstr
+        let fstr=s:F.rewritefname(a:plugdict.sid, d.Function)
+        if string(+fstr)==#fstr
             continue
         endif
         try
 endfunction
 call s:F._frawor.newfeature('delfunctions', {'unloadpre': s:F.delfunctions,
             \                               'ignoredeps': 1})
+"▶1 saver
+let s:F.saver={}
+let s:F.saver.view=function('winsaveview')
+"▶2 buffer
+function s:F.saver.buffer()
+    return bufnr('%')
+endfunction
+"▶1 setter
+let s:F.setter={}
+let s:F.saver.view=function('winrestview')
+"▶2 buffer
+function s:F.setter.buffer(bufnr)
+    if a:bufnr!=bufnr('%')
+        execute "buffer ".a:bufnr
+    endif
+endfunction
+"▶1 wrapfunc
+function s:F.wrapfunc(plugdict, fopts, fdictsname, fname)
+    "▶2 Check a:fopts
+    if type(d.fopts)!=type({})
+        call s:F._frawor.throw('foptsnotdict', a:fname, a:plugdict.id)
+    elseif !has_key(a:fopts, 'function')
+        call s:F._frawor.throw('nofunc', a:fname, a:plugdict.id)
+    endif
+    "▲2
+    let fdicts=eval(a:fdictsname)
+    let fdef  =   {"id": a:fname,
+                \"plid": a:plugdict.id}
+    let fdicts[fdef.id]=fdef
+    "▶2 Add 'function', 'checker' and 'filter' keys
+    for key in ["function", "checker", "filter"]
+        if has_key(a:fopts, key)
+            if type(a:fopts[key])!=2
+                call s:F._frawor.throw('nref', a:fname, a:plugdict.id, key)
+            endif
+            let fstr=s:F.rewritefname(a:plugdict.sid, a:fopts[key])
+            if string(+fstr)!=#fstr
+                if !exists('*'.fstr)
+                    call s:F._frawor.throw('uref', a:plugdict.id, key, a:fname)
+                endif
+                let fdef[key]=function(fstr)
+            else
+                let fdef[key]=a:fopts[key]
+            endif
+        endif
+    endfor
+    "▶2 Add 'altervars' key
+    if has_key(a:fopts, 'altervars')
+        if type(a:fopts.altervars)!=type([])
+            call s:F._frawor.throw('altnlst', a:fname, a:plugdict.id)
+        endif
+        let fopts.altervars=map(copy(a:fopts.altervars), 'copy(v:val)')
+    endif
+    "▶2 Define variables
+    let args='a:000'                         " Contains name of variable that 
+                                             " holds arguments
+    let indent=""
+    let fpref=a:fdictsname.'["'.a:fname.'"]' " Contains function options name
+    "▲2
+    let func="function ".a:fname."(...)\n"   " Contains function string
+    "▶2 Process `altervars' key
+    if has_key(fdef, 'altervars')
+        let func.=indent."let d={}\n".
+                    \indent."try\n"
+        "▶3 Define variables
+        let indent.="    "
+        let i=0
+        let laltvars=len(fdef.altervars)
+        "▲3
+        while i<laltvars
+            "▶3 Check current element
+            if type(fdef.altervars[i])!=type([])
+                call s:F._frawor.throw('altelnlst', a:plugdict.id, a:fname, i)
+            elseif empty(fdef.altervars[i]) || len(fdef.altervars[i])>2
+                call s:F._frawor.throw('altelinvlen', a:plugdict.id, a:fname, i,
+                            \                         len(fdef.altervars[i]))
+            elseif type(fdef.altervars[i][0])!=type("")
+                call s:F._frawor.throw('altnstr', a:plugdict.id, a:fname, i)
+            endif
+            "▲3
+            let element=fdef.altervars[i]
+            let varname=element[0]
+            let hasvar=(len(element)>1)
+            "▶3 Process variables
+            if varname[1]==#':' && index(['g', 'b', 't', 'w'], varname[0])!=-1
+                "▶4 Check variable name
+                if varname[2:]!~#'^\h\w*$'
+                    call s:F._frawor.throw('invvname', a:plugdict.id, a:fname,
+                                \                      i, varname[2:])
+                endif
+                "▲4
+                let func.=indent."if exists('".varname."')\n".
+                            \indent."    let d.".i."=".varname."\n"
+                if hasvar
+                    " Unlet variable to be sure that it won't cause E706 
+                    " (variable type mismatch) error
+                    let func.=indent."    unlet ".varname."\n".
+                                \indent."endif\n".
+                                \indent."let ".varname."="
+                    if type(element[1])==type(0) ||
+                                \(has("float") && type(element[1])==type(0.0) &&
+                                \ string(element[1])!~'\w')
+                        let func.=string(element[1])
+                    else
+                        let func.=fpref.".altervars[".i."]"
+                    endif
+                    let func.="\n"
+                else
+                    let func.=indent."endif\n"
+                endif
+            "▶3 Process options
+            elseif varname[0]==#'&'
+                "▶4 Check option name
+                if varname[2]!=#':' || !(varname[1]==#'g' || varname[1]==#'l')
+                    call s:F._frawor.throw('noocpref', a:plugdict.id, a:fname,
+                                \                      i, varname)
+                elseif varname[3:]!~#'^\l\+$'
+                    call s:F._frawor.throw('invoname', a:plugdict.id, a:fname,
+                                \                      i, varname[3:])
+                endif
+                "▲4
+                if exists('+'.varname[1:])
+                    let func.=indent."let d.".i."=".varname."\n"
+                    if hasvar
+                        if type(element[1])!=#type("") &&
+                                    \type(element[1])!=type(0)
+                            call s:F._frawor.throw('invoval', a:plugdict.id,
+                                        \                     a:fname, i)
+                        endif
+                        let func.=indent."let ".varname."=".
+                                    \           fpref.".altervars[".i."]\n"
+                    endif
+                endif
+            "▶3 Process special
+            elseif varname[0]==#'+'
+                "▶4 Check special name
+                if !has_key(s:F.saver, varname[1:])
+                    call s:F._frawor.throw('usaver', a:plugdict.id, a:fname, i,
+                                \                    varname[1:])
+                endif
+                "▲4
+                let sname=varname[1:]
+                let func.=indent."let d.".i."=s:F.saver.".sname."()\n"
+                if hasvar
+                    let func.=indent."call s:F.setter.".sname."(".
+                                \               fpref.".altervars[".i."])\n"
+                endif
+            "▶3 Process unknown
+            else
+                call s:F._frawor.throw('ualt', a:plugdict.id, a:fname, i,
+                            \                  varname)
+            endif
+            "▲3
+            let i+=1
+        endwhile
+    endif
+    "▶2 Process 'checker' and 'filter' keys
+    if has_key(fdef, 'checker')
+        let func.=indent."if !call(".fpref.".checker, [".args."], {})\n".
+                    \indent."    call s:F._frawor.throw('checkfailed', ".
+                    \                                   fpref.".id, ".
+                    \                                   fpref.".plid)\n".
+                    \"endif\n"
+    endif
+    if has_key(fdef, 'filter')
+        let func.=indent."let args=call(".fpref.".filter, [".args."], {})\n".
+                    \indent."if type(args)!=".type([])."\n".
+                    \indent."    call s:F._frawor.throw('filterfailed', ".
+                    \                                   fpref.".id, ".
+                    \                                   fpref.".plid)\n".
+                    \indent."endif\n"
+        let args="args"
+    endif
+    "▲2
+    let func.=indent."return call(".fpref.".function, ".args.", {})\n"
+    "▶2 Process `altervars' key (:finally clause)
+    if has_key(fdef, 'altervars')
+        let func.=indent[:-5]."finally\n"
+        "▶3 Define variables
+        let i=0
+        " laltvars was defined in previous if(altervars) block
+        "▲3
+        while i<laltvars
+            " No need to check anything here: everything was checked in previous 
+            " if(altervars) block
+            "▶3 Process options
+            if varname[0]==#'&'
+                if exists('+'.varname[1:])
+                    let func.=indent."let ".varname."=d.".i."\n"
+                endif
+            "▶3 Process special
+            elseif varname[0]==#'+'
+                let func.=indent."call s:F.setter.".varname[1:]."(d.".i.")\n"
+            "▶3 Process variables
+            else
+                " Restore the previous status: variable will be left undefined 
+                " if it was not defined before function was run
+                let func.=indent."if exists('".varname."')\n".
+                            \indent."    unlet ".varname."\n".
+                            \indent."endif\n".
+                            \indent."if has_key(d, ".i.")\n".
+                            \indent."    let ".varname."=d.".i."\n".
+                            \indent."endif\n"
+            endif
+            "▲3
+            let i+=1
+        endwhile
+        let indent=indent[:-5]
+        let func.=indent."endtry\n"
+    endif
+    "▲2
+    return fdef
+endfunction
 "▶1 addextfunctions
+let s:g.functions={}
 function s:F.addextfunctions(plugdict, functions)
     if type(a:function)!=type({})
         call s:F._frawor.throw('fnotdict', a:plugdict.id)
     endif
     let d={}
     for [fname, d.fopts] in items(a:function)
+        "▶2 Check function name
+        if fname!~#'^\%(\h:\w\+\|[A-Z_]\w*\)$'
+            call s:F._frawor.throw('invfname', a:plugdict.id, fname)
+        elseif has_key(s:g.functions, fname)
+            call s:F._frawor.throw('fdef', fname, a:plugdict.id,
+                        \                  fdicts[fname].plid)
+        endif
+        "▲2
+        call s:F.wrapfunc(a:plugdict, d.fopts, 's:g.functions', fname)
     endfor
 endfunction
 call s:F._frawor.newfeature('addextfunctions', {'cons': s:F.addextfunctions})