Commits

ZyX_I  committed 266544f

@/fwc: Started working on completion implemenation:
- Added `first' internal function
- Added completion of {required} section
- Added completion for `in', `key', `take', `either' and `first' internal
functions

  • Participants
  • Parent commits 14cef93

Comments (0)

Files changed (9)

 
 either {arg}*    (|FWC-{arg}|)                                 *FWC-c-either*
     Checker: succeeds if any of given {arg}s succeeds.
+    Completer: adds all {arg}s to variants list.
+
+first {arg}*   (|FWC-{arg}|)                                     *FWC-c-first*
+    Checker: same as |FWC-c-either|.
+    Completer: processes {arg}s until list is non-empty or no more {arg}s 
+               left.
 
 tuple {arg}*    (|FWC-{arg}|)                      *FWC-c-tuple* *FWC-f-tuple*
     Checks whether argument is a list with a fixed (equal to number of {arg}s) 

File plugin/frawor/fwc/compiler.vim

         endfor
         call filter(sq, '!empty(v:val)')
         for key in filter(['actions', 'optional'], 'has_key(sq, v:val)')
-            call map(copy(sq[key]), 's:F.cleanup(v:val)')
+            call map(sq[key], 's:F.cleanup(v:val)')
             call filter(sq[key], '!empty(v:val)')
             if empty(sq[key])
                 call remove(sq, key)
             call map(sq.optional, 'v:val[1]')
         endif
     endif
+    return a:list
 endfunction
 "▶1 getlenrange     :: adescr → (minlen, maxlen) + adescr
 function s:F.getlenrange(adescr)
     endfor
     return r
 endfunction
+"▶1 getlastsub      :: () + self → String
+function s:compiler.getlastsub()
+    return self.getsub(self.subs[-1])
+endfunction
 "▶1 getfunc         :: funccontext, split[, addarg, ...] + self → String
 function s:compiler.getfunc(func, split, ...)
     if a:split
     endif
     return self.getvar(a:str)
 endfunction
+"▶1 addmatches      :: &self(ldstr, exptype)
+function s:compiler.addmatches(ldstr, exptype)
+    if self.joinlists
+        call add(self.ldstrs, [a:ldstr, a:exptype])
+        return self
+    else
+        return self.increment('@-@', self.getmatcher(self.matcher, a:ldstr,
+                    \                                self.comparg))
+    endif
+endfunction
+"▶1 addjoinedmtchs  :: &self
+function s:compiler.addjoinedmtchs()
+    if !self.joinlists
+        let lststrs=[]
+        let curldbase=self.getlvarid('curld').'_'
+        let ambstrs=[]
+        let i=0
+        for [ldstr, exptype] in remove(self.ldstrs, 0, -1)
+            if exptype==type([])
+                call add(lststrs, ldstr)
+            elseif exptype==type({})
+                call add(lststrs, 'sort(keys('.ldstr.'))')
+            else
+                let curldstr=curldbase.i
+                call add(ambstrs, curldstr)
+                call self.let(curldstr, ldstr)
+                call add(lststrs, '((type('.curldstr.')=='.type([]).')?'.
+                            \           '('.curldstr.'):'.
+                            \           '(sort(keys('.curldstr.'))))')
+            endif
+            let i+=1
+        endfor
+        call self.addmatches(join(lststrs, '+'), type([]))
+        if !empty(ambstrs)
+            call self.unlet(ambstrs)
+        endif
+    endif
+    return self
+endfunction
 "▶1 addthrow        :: &self(msg::String, msgarg, needcurarg, ...)
 function s:compiler.addthrow(msg, needcurarg, ...)
     let args=self.string(a:msg).', '
 endfunction
 "▶1 compilemsg      :: &self(msgcontext, _)
 function s:compiler.compilemsg(msg, idx)
+    if self.type is 'complete'
+        return self
+    endif
     if a:msg[1] is 0
         call add(self.msgs.own, '')
         return self
 endfunction
 "▶1 compilepipe     :: &self(pipecontext, idx)
 function s:compiler.compilepipe(pipe, idx)
-    let curargstr=self.argstr()
     "▶2 `func' pipe
     if a:pipe[1][0] is 'func'
-        call self.let(curargstr, self.getfunc(a:pipe[1],0, curargstr))
-        let self.typechanged=1
+        let curargstr=self.argstr()
+        if self.type is 'check' || self.type is 'filter'
+            call self.let(curargstr, self.getfunc(a:pipe[1],0, curargstr))
+            let self.typechanged=1
+        endif
     "▶2 `expr' pipe
     elseif a:pipe[1][0] is 'expr'
-        call self.let(curargstr, self.getexpr(a:pipe[1], curargstr))
-        let self.typechanged=1
+        let curargstr=self.argstr()
+        if self.type is 'check' || self.type is 'filter'
+            call self.let(curargstr, self.getexpr(a:pipe[1], curargstr))
+            let self.typechanged=1
+        endif
     "▶2 Built-in pipes
     elseif a:pipe[1][0] is 'intfunc'
         let desc=a:pipe[1][1:]
-        if has_key(s:_r.FWC_intfuncs[desc[0]], 'pipe')
-            call call(s:_r.FWC_intfuncs[desc[0]].pipe, [desc,a:idx,'pipe'],self)
-        else
-            call call(s:_r.FWC_intfuncs[desc[0]].check,[desc,a:idx,'pipe'],self)
-        endif
-        if has_key(s:_r.FWC_intfuncs[desc[0]], 'optimize')
-            call call(s:_r.FWC_intfuncs[desc[0]].optimize, [a:idx], self)
+        if self.type is 'check' || self.type is 'filter'
+            if has_key(s:_r.FWC_intfuncs[desc[0]], 'pipe')
+                call call(s:_r.FWC_intfuncs[desc[0]].pipe, [desc,a:idx, 'pipe'],
+                            \self)
+            else
+                call call(s:_r.FWC_intfuncs[desc[0]].check,[desc,a:idx, 'pipe'],
+                            \self)
+            endif
+            if has_key(s:_r.FWC_intfuncs[desc[0]], 'optimize')
+                call call(s:_r.FWC_intfuncs[desc[0]].optimize, [a:idx], self)
+            endif
+        elseif has_key(s:_r.FWC_intfuncs[dname], 'complete')
+            call call(s:_r.FWC_intfuncs[dname].complete, [desc, a:idx, 'pipe'],
+                        \self)
         endif
     endif
     "▲2
 let s:compiler.compilefilter=s:compiler.compilepipe
 "▶1 compilecheck    :: &self(checkcontext, idx)
 function s:compiler.compilecheck(check, idx)
-    let curargstr=self.argstr()
-    let check=a:check[1]
     "▶2 `func' check
-    if check[0] is 'func'
-        call self.nextthrow(self.getfunc(check, 0, curargstr).' is 0',
-                    \       'funcfail', a:idx)
+    if a:check[1][0] is 'func'
+        if self.type is 'check' || self.type is 'filter'
+            call self.nextthrow(self.getfunc(a:check[1], 0, self.argstr()).
+                        \                                               ' is 0',
+                        \       'funcfail', a:idx)
+        endif
     "▶2 `expr' check
-    elseif check[0] is 'expr'
-        call self.nextthrow(self.getexpr(check, curargstr).' is 0',
-                    \       'exprfail', a:idx)
+    elseif a:check[1][0] is 'expr'
+        if self.type is 'check' || self.type is 'filter'
+            call self.nextthrow(self.getexpr(a:check[1], self.argstr()).' is 0',
+                        \       'exprfail', a:idx)
+        endif
     "▶2 Built-in checks
     else
-        let desc=check[1:]
+        let desc=a:check[1][1:]
         let dname=desc[0]
-        if !has_key(s:_r.FWC_intfuncs[dname], 'check')
-            call s:_f.throw('ucheck', dname)
-        endif
-        call call(s:_r.FWC_intfuncs[dname].check, [desc, a:idx, 'check'], self)
-        if has_key(s:_r.FWC_intfuncs[dname], 'optimize')
-            call call(s:_r.FWC_intfuncs[dname].optimize, [a:idx], self)
+        if self.type is 'check' || self.type is 'filter'
+            if !has_key(s:_r.FWC_intfuncs[dname], 'check')
+                call s:_f.throw('ucheck', dname)
+            endif
+            call call(s:_r.FWC_intfuncs[dname].check, [desc, a:idx, 'check'],
+                        \self)
+            if has_key(s:_r.FWC_intfuncs[dname], 'optimize')
+                call call(s:_r.FWC_intfuncs[dname].optimize, [a:idx], self)
+            endif
+        elseif has_key(s:_r.FWC_intfuncs[dname], 'complete')
+            call call(s:_r.FWC_intfuncs[dname].complete, [desc, a:idx, 'check'],
+                        \self)
         endif
     endif
     "▲2
     return self
 endfunction
-"▶1 compilearg     :: argcontext, idx[, defcomptype] + self → self + self
+"▶1 compilecomplete :: &self(completecontext, idx)
+function s:compiler.compilecomplete(complete, idx)
+    if self.type is 'complete' && !(a:complete[1][0] is 'func' ||
+                \                   a:complete[1][0] is 'expr')
+        let desc=a:complete[1][1:]
+        let dname=desc[0]
+        if has_key(s:_r.FWC_intfuncs[dname], 'complete')
+            call call(s:_r.FWC_intfuncs[dname].complete,
+                        \[desc, a:idx, 'complete'], self)
+        endif
+    endif
+    return self
+endfunction
+"▶1 compilearg      :: &self(argcontext, idx[, defcomptype])
 function s:compiler.compilearg(argcon, idx, ...)
     let type=get(a:000, 0, self.type)
     if a:argcon[0] is 'arg'
         if comptype is 'intfunc'
             let comptype=type
             let compargs[0]=[comptype, compargs[0]]
+            if self.type is 'complete' &&
+                        \get(s:_r.FWC_intfuncs[proc[1]], 'stopcomp', 0)
+                break
+            endif
         elseif comptype is 'defval'
             continue
         endif
     "▶2 Length checks, lagsstr and nextsub variables
     if !empty(self.subs)
         let largsstr=self.getlargsstr()
-        if !has_key(a:adescr, 'minimum')
-            call s:F.getlenrange(a:adescr)
-        endif
-        if !has_key(a:adescr, 'checkedfor')
-            call self.addlencheck(a:adescr.minimum, ((purgemax)?
-                        \                               (-1):
-                        \                               (a:adescr.maximum)))
-            let a:adescr.checkedfor=1
+        if self.type is 'check' || self.type is 'filter'
+            if !has_key(a:adescr, 'minimum')
+                call s:F.getlenrange(a:adescr)
+            endif
+            if !has_key(a:adescr, 'checkedfor')
+                call self.addlencheck(a:adescr.minimum, ((purgemax)?
+                            \                               (-1):
+                            \                               (a:adescr.maximum)))
+                let a:adescr.checkedfor=1
+            endif
         endif
         let nextsub=copy(self.subs[-1])
     endif
             else
                 let idx=''
             endif
+            if self.type is 'complete' && !self.o.only
+                call self.addif(largsstr.'-1 == '.self.getlastsub())
+            endif
             call self.compilearg(arg, idx)
             call self.incsub()
+            if self.type is 'complete' && !self.o.only
+                call self.up()
+            endif
         endfor
         if !empty(self.subs)
             let nextsub=copy(self.subs[-1])
     " XXX a:0 is checked here
     if !a:0 && type(self.subs[-1])==type([])
         let largsstr=self.getlargsstr()
-        let proclen=self.getsub(self.subs[-1])
+        let proclen=self.getlastsub()
         call self.addif(proclen.' isnot '.largsstr)
                     \.addthrow('lennmatch', 0, proclen, largsstr)
     endif
     return self
 endfunction
 "▶1 compstr         :: vars, String, type, doreturn → [String]
+let s:defcompletematcher=['matcher', ['intfunc', 'smart', 2]]
 function s:F.compstr(vars, string, type, doreturn)
     "▶2 Setup self
     let t=extend(s:_r.new_constructor(), {
                 \'defvals': [],
                 \'typechanged': 0,
             \})
-    let tree=s:_r.fwc_parser(a:string, a:type)[1:]
     call extend(t, s:compiler, 'error')
+    if !(t.type is 'check' || t.type is 'filter')
+        let t.matcher=s:defcompletematcher
+        let t.comparg=t.argbase.t.getsubs([-1])
+        let t.joinlists=0
+        let t.ldstrs=[]
+    endif
     "▲2
+    let [t.o, tree]=s:F.cleanup(s:_r.fwc_parser(a:string, a:type)[1:])
     if a:doreturn is 1
         let t.failcal=['return', 0]
     else
-        let t.failcal=['call', '@%@.F.throw('.a:doreturn.')']
+        let t.failcal=['call', '@%@.F.throw('.join(map(a:doreturn,
+                    \                                  't.string(v:val)'),', ').
+                    \                     ')']
     endif
-    call s:F.cleanup(tree)
-    let t.o=remove(tree, 0)
+    if !t.o.only
+        call add(t.subs, 0)
+    endif
     if t.type is 'check' || t.type is 'filter'
         call        t.let('@$@messages',  '[]')
                     \.let('@$@pmessages', '[]')
                     \.try()
-        if !t.o.only
-            call add(t.subs, 0)
-        endif
-        call t.compadescr(tree[0])
+        call t.compadescr(tree)
                 \.up().finally()
                     \.for('@$@targs', '@$@messages')
                         \.call('call(@%@.F.warn, @$@targs, {})').up()
                         \.call('call(@%@.p._f.warn, @$@targs, {})').up()
                 \.up().up()
     else
-        call t.let('@-@', '[]')
+        call t.let('@-@', '[]').compadescr(tree)
     endif
     if a:doreturn is 1
         if t.type is 'check'
     endif
     "▲2
     let vars=s:F.createvars(a:plugdict.g)
-    let a='"FWCfail", '.self.string(a:fname).', '.self.string(a:plugdict.id)
+    let a=['FWCfail', a:fname, a:plugdict.id]
     let [opts, lines]=s:F.compstr(vars, a:arg[0], a:arg[1], a)
     if opts.only
         call map(lines, 'substitute(v:val, "@@@", "@$@args", "g")')

File plugin/frawor/fwc/constructor.vim

 endfunction
 "▶1 unlet      :: &self(var)
 function s:constructor.unlet(var)
-    return self.out().deeper('unlet', a:var).up()
+    return self.out().deeper('unlet', type(a:var)==type('')?[a:var]:a:var).up()
 endfunction
 "▶1 increment  :: &self(var[, val])
 function s:constructor.increment(var, ...)
                 call add(r, istr.'let '.lhs.assign.shift)
             elseif       type is 'call'   ||
                         \type is 'throw'  ||
-                        \type is 'return' ||
-                        \type is 'unlet'
+                        \type is 'return'
                 call add(r, istr.type.' '.remove(item, 0))
+            elseif type is 'unlet'
+                call add(r, istr.type.' '.join(remove(item, 0)))
             elseif type is 'continue' || type is 'break'
                 call add(r, istr.type)
             endif

File plugin/frawor/fwc/intfuncs.vim

     call self.addif(sucstr).addrestmsgs().up().popms()
                 \.addif().addthrow('eitherfail', 1, a:idx).up().up()
 endfunction
+function s:r.either.complete(desc, idx, type)
+    let self.joinlists+=1
+    let i=0
+    for arg in a:desc[1]
+        call self.compilearg(arg, a:idx.'(either).'.i, 'check')
+        let i+=1
+    endfor
+    let self.joinlists-=1
+    call self.addjoinedmtchs()
+    return self
+endfunction
+"▶1 `first'
+" Same as `either', except for completion
+let s:r.first=copy(s:r.either)
+unlet s:r.first.complete
+function s:r.first.complete(desc, idx, type)
+    if !empty(a:desc[1])
+        call self.compilearg(a:desc[1][0], a:idx.'(either).0', 'check')
+    endif
+    let i=1
+    for arg in a:desc[1][1:]
+        call self.if('empty(@-@)')
+                    \.compilearg(arg, a:idx.'(either).'.i, 'check')
+                \.up().endif()
+        let i+=1
+    endfor
+    return self
+endfunction
 "▶1 `tuple'
 let s:r.tuple={'args': ['*arg']}
 "▶2 addtuple       :: tupledesc, idx, defaultArgType + self → self + self
                     \.let(curargstr, matchstr)
     endif
 endfunction
+function s:r.in.complete(desc, idx, type)
+    return self.addmatches(self.getvar(a:desc[1]), type([]))
+endfunction
 "▶1 `key'
 let s:r.key={'args': ['var', '?omtchr']}
 " Checks whether {argument} is a key of {var}. Matcher is ignored
 endfunction
 " Picks up first key from {var}::Dictionary that matches {argument}
 let s:r.key.pipe=s:r.in.pipe
+function s:r.key.complete(desc, idx, type)
+    return self.addmatches(self.getvar(a:desc[1]), type({}))
+endfunction
 "▶1 `take'
 " Replaces {argument} with value of the first key from {var}::Dictionary that 
 " matches {argument}
     let self.typechanged=1
     return self
 endfunction
+let s:r.take.complete=s:r.key.complete
 "▶1 `substitute'
 " Runs substitute on {argument}
 let s:r.substitute={'args': ['reg', 'string', 'string']}
         elseif a:ignorecase
             let list=sort(keys(a:ld))
         else
-            return 0
+            return ((a:acceptfirst is 2)?([]):(0))
         endif
     else
         if index(a:ld, a:str)!=-1
         elseif a:ignorecase
             let list=a:ld
         else
-            return 0
+            return ((a:acceptfirst is 2)?([]):(0))
         endif
     endif
     let idx=index(list, a:str, 0, 1)
     if idx==-1
-        return 0
+        return ((a:acceptfirst is 2)?([]):(0))
     elseif a:acceptfirst is 2
         let r=[list[idx]]
         while 1
                 elseif empty(r)
                     call add(r, value)
                 else
-                    return 0
+                    return ((a:acceptfirst is 2)?([]):(0))
                 endif
             endif
         endif
             elseif a:acceptfirst || len(r)==1
                 return r[0]
             else
-                return 0
+                return ((a:acceptfirst is 2)?([]):(0))
             endif
         endif
     endfor
-    return 0
+    return ((a:acceptfirst is 2)?([]):(0))
 endfunction
 "▶1 Register resource
 call s:_f.postresource('FWC_intfuncs', s:r, 1)

File plugin/frawor/fwc/topconstructs.vim

 let s:cfreg='\v^'.s:cf.'$'
 let s:actdefmatcher=['matcher', ['intfunc', 'start', 0, 0]]
 let s:prefdefmatcher=s:actdefmatcher
-"▶1 addprefix      :: preflist, prefix → _ + preflist
+"▶1 addprefix :: preflist, prefix → _ + preflist
 function s:F.addprefix(preflist, prefix)
     if index(a:preflist, a:prefix)!=-1
         call self._throw('ambprefix', a:prefix)
     endif
     call add(a:preflist, a:prefix)
 endfunction
+"▶1 hasnext   :: adescr, cursection → Bool
+function s:F.hasnext(adescr, cursection)
+    let sectidx=index(s:r._order, a:cursection)
+    return !empty(filter(s:r._order[(sectidx+1):], 'has_key(a:adescr, v:val)'))
+endfunction
 "▶1 optional
 let s:r.optional={'char': '['}
 "▶2 scan
     if lopt>1
         call self.let(failstr, 0)
     endif
-    let newsub=self.getsub(self.subs[-1])
+    let newsub=self.getlastsub()
     if newsub isnot a:caidxstr
         call self.let(a:caidxstr, newsub)
     endif
                             \.popms()
                             \.addrestmsgs(1)
                             \.let(failstr, 0)
-            let newsub=self.getsub(self.subs[-1])
+            let newsub=self.getlastsub()
             if newsub isnot a:caidxstr
                 call self.let(a:caidxstr, newsub)
             endif
     let lists=filter(copy(a:adescr.prefixes), 'v:val[1].list')
     let haslist=!empty(lists)
     let lastliststr=self.getlvarid('lastlist')
-    let addtry=(has_key(a:adescr, 'next') || has_key(a:adescr, 'actions'))
+    let addtry=s:F.hasnext(a:adescr, 'prefixes')
     "▶3 Add messages saving if required
     if addtry
         if !addedsavemsgs
 function s:r.next.compile(adescr, idx, caidxstr, largsstr, purgemax,
             \             nextsub, addedsavemsgs)
     let addedsavemsgs=a:addedsavemsgs
-    if has_key(a:adescr, 'actions')
+    let hasnext=s:F.hasnext(a:adescr, 'next')
+    if hasnext
         if !addedsavemsgs
             call self.addsavemsgs()
             let addedsavemsgs=1
                 \.compilearg(a:adescr.next[0], a:idx.'.(next)')
                 \.increment(a:caidxstr)
                 \.up()
-    if has_key(a:adescr, 'actions')
+    if hasnext
         call self.addrestmsgs(1).catch(s:cfreg).popms().up()
     endif
     return [[a:caidxstr], addedsavemsgs]
         if len(action)>1
             call self.compadescr(action[1],idx.'.'.self.string(action[0]))
         endif
-        call self.let(a:caidxstr, self.getsub(self.subs[-1]))
-                    \.up()
+        call self.let(a:caidxstr, self.getlastsub()).up()
         let self.subs[-1]=copy(astartsub)
     endfor
     if noact isnot 0
         call self.incsub(-1)
         if len(noact)>1
             call self.compadescr(noact[1], idx.'.nullact')
-                        \.let(a:caidxstr, self.getsub(self.subs[-1]))
+                        \.let(a:caidxstr, self.getlastsub())
         endif
         let self.subs[-1]=copy(astartsub)
         call self.up().up()

File test/fwccomplete.in

+:let &rtp.=",".escape($TESTDIR, ',\').'/rtp'
+:let g:testfile="plugin/".g:curtest.".vim"
+:source test.vim

File test/fwccomplete.ok

+>>> messages
+::: Section <Built-in completion functions/in>
+::: Section <Built-in completion functions/key>
+::: Section <Built-in completion functions/take>
+::: Section <Built-in completion functions/either>
+::: Section <Built-in completion functions/first>
+::: Section <Different sections/{required}>
+<<< messages

File test/fwccompletetests.dat

+!complete
+:let s:list=['abc', 'ab-c', 'def-ghi', 'adb']
+:let s:list2=['abc', 'def-b', 'geh']
+:let s:dict={'abc': 1, 'adb': 1, 'aef': 1}
+#▶1 Built-in completion functions
+#▶2 in
+`in list
+  @+
+  =s:list
+
+  @b
+  abc ab-c adb
+
+  @j
+  =[]
+#▶2 key
+`key dict
+  @+
+  =sort(keys(s:dict))
+
+  @a
+  abc adb aef
+
+  @g
+  =[]
+#▶2 take
+`take dict exact
+  @+
+  =sort(keys(s:dict))
+
+  @a
+  abc adb aef
+
+  @g
+  =[]
+#▶2 either
+`either in list, key dict
+  @+
+  =s:list+sort(keys(s:dict))
+
+  @d
+  def-ghi
+#▶2 first
+`first in list, key dict
+  @+
+  =s:list
+
+  @d
+  def-ghi
+
+  @ae
+  aef
+#▶1 Different sections
+#▶2 {required}
+`in list
+  @+
+  =s:list
+
+  @b+
+  =[]
+`in list in list2
+  @+
+  =s:list
+
+  @b+
+  =s:list2
+# vim: cms=#%s fmr=▶,▲ sw=2 ts=2 sts=2 et

File test/rtp/plugin/fwccomplete.vim

+execute frawor#Setup('0.0', {'@/fwc/compiler': '0.0',
+            \                '@/os':           '0.0'}, 1)
+let os=s:_r.os
+let i=1
+let j=1
+let sections=[]
+let prevsections=[]
+let toexecute=''
+let args=[]
+let FWC=''
+let FWCtype=''
+function ToList(line)
+    if a:line[0] is '='
+        return eval(a:line[1:])
+    else
+        if a:line[-1:] is '+'
+            return split(a:line[:-2])+['']
+        else
+            return split(a:line)
+        endif
+    endif
+endfunction
+for line in readfile('fwccompletetests.dat')
+    let line=substitute(line, '^\s*', '', '')
+    if empty(line)
+        continue
+    elseif line[0] is '#'
+        if char2nr(line[1:])==0x25b6
+            let level=+line[4:]
+            if level<=len(sections)
+                call remove(sections, level-1, -1)
+            endif
+            call add(sections, line[4+len(level)+1:])
+            let i=1
+        endif
+        continue
+    elseif line[0] is ':'
+        execute toexecute."\n".line[1:]
+        let toexecute=''
+        continue
+    elseif line[0] is '\'
+        let toexecute.="\n".line[1:]
+        continue
+    elseif line[0] is '@'
+        let args=[ToList(line[1:])]
+        continue
+    elseif line[0] is '`'
+        let FWC=line[1:]
+        continue
+    elseif line[0] is '!'
+        let FWCtype=line[1:]
+        continue
+    elseif       (!empty($DEBUGTEST) && j!=$DEBUGTEST) ||
+                \(!empty($STARTFROM) && j<$STARTFROM)
+        let i+=1
+        let j+=1
+        continue
+    endif
+    if prevsections!=#sections
+        let prevsections=copy(sections)
+        echom '::: Section <'.join(sections, '/').'>'
+    endif
+    let compargs=[FWC, FWCtype]
+    let result=ToList(line)
+    if !empty($DEBUGTEST)
+        debug let ChFunc=call(s:_f.fwc_compile, compargs, {})
+        debug let realres=call(ChFunc, copy(args), {})
+    else
+        let ChFunc=call(s:_f.fwc_compile, compargs, {})
+        let realres=call(ChFunc, copy(args), {})
+    endif
+    if type(realres)!=type(result) || realres!=result
+        echom '[[[ Test #'.i.'('.j.') '.
+                    \'in section <'.join(sections, '/').'> failed:'
+        echom 'expected '.string(result).','
+        echom 'but got  '.string(realres)
+        echom 'Checked arguments: '.string(args)
+        echom 'FWC string: '.compargs[0]
+        echom 'Check type: '.compargs[1]
+        function g:.ChFunc
+        echom ']]]'
+        break
+    endif
+    let i+=1
+    let j+=1
+    unlet realres result
+endfor
+" vim: fmr=▶,▲