Commits

ZyX_I committed 402a732

@/fwc: Added `dict' check and pipe
Forced dots between subscript components
Made `?' denote `check' key description in {ddescr}
Added `func', `expr' and `any' key descriptions to {ddescr}

Comments (0)

Files changed (4)

plugin/frawor/fwc/compiler.vim

             \              'contained in a writable directory',
             \     'nread': '%s is not readable',
             \ 'nexecable': '%s is not executable',
+            \ 'keynmatch': 'key `%s'' does not match any specification',
         \}, '"Error while processing check %s for %s: ".v:val'))
 let s:_messages._types=['number', 'string', 'function reference', 'list',
             \           'dictionary']
         endif
     endif
 endfunction
-"▶1 addthrow       :: msg::String, msgarg, ... + self → self + self
-function s:constructor.addthrow(msg, ...)
-    let args=copy(a:000)
-    let curidx=index(args, '@#@')
-    if curidx!=-1
-        let args=map(args[:(curidx-1)], 'string(v:val)')+[self.argstr(1)]+
-                    \args[(curidx+1):]
-    endif
-    return self.add('call add(@$@messages, ['.string(a:msg).', '.
-                \                             join(args, ', ').'])',
-                \   'throw "CHECKFAILED"').up()
-endfunction
-"▶1 nextthrow      :: condition::expr, throwargs + self → self + self
-function s:constructor.nextthrow(cond, ...)
-    return call(self.nextcond(a:cond).addthrow, a:000, self)
-endfunction
-"▶1 getlenrange    :: adescr → (minlen, maxlen) + adescr
-function s:F.getlenrange(adescr)
-    let minimum=0
-    let maximum=0
-    "▶2 “arg” key
-    if has_key(a:adescr, 'arg')
-        let minimum=len(a:adescr.arg)
-        let maximum=minimum
-    endif
-    "▶2 “optional” key
-    if has_key(a:adescr, 'optional')
-        let lenlist=map(copy(a:adescr.optional), 's:F.getlenrange(v:val)[1]')
-        if index(lenlist, -1)!=-1
-            let maximum=-1
-        else
-            let maximum+=max(lenlist)
-        endif
-    endif
-    "▶2 “actions” key
-    if has_key(a:adescr, 'actions')
-        let minset=0
-        let amin=0
-        let amax=0
-        let has0=0
-        let hasa=0
-        for action in a:adescr.actions
-            if action[0] is 0
-                let has0=1
-            else
-                let hasa=1
-            endif
-            if len(action)>1
-                let [namin, namax]=s:F.getlenrange(action[1])
-                if namin<amin || !minset
-                    let minset=1
-                    let amin=namin
-                endif
-                if namax==-1
-                    let maximum=-1
-                elseif namax>amax
-                    let amax=namax
-                endif
-            endif
-        endfor
-        let minimum+=((has0)?(0):(hasa))+amin
-        if maximum!=-1
-            let maximum+=hasa+amax
-        endif
-    endif
-    "▶2 “prefixes” key
-    if has_key(a:adescr, 'prefixes')
-        let maximum=-1
-        for prefopts in map(copy(a:adescr.prefixes), 'v:val[1]')
-            let minimum+=((prefopts.opt)?
-                        \   (0):
-                        \   ((prefopts.list)?
-                        \       (2):
-                        \       (1+prefopts.argnum)))
-        endfor
-    endif
-    "▶2 “next” key
-    if has_key(a:adescr, 'next')
-        let maximum=-1
-    endif
-    "▲2
-    let a:adescr.minimum=minimum
-    let a:adescr.maximum=maximum
-    return [minimum, maximum]
-endfunction
-"▶1 lencheck       :: minlen, maxlen + self → self + self
-function s:constructor.lencheck(minimum, maximum)
-    if a:maximum==a:minimum
-        call self.nextthrow('@$@largs isnot '.a:maximum,
-                    \       'invlen', a:minimum, '@$@largs')
-    else
-        if a:minimum>0
-            call self.nextthrow('@$@largs<'.a:minimum,
-                        \       'tooshort', a:minimum, '@$@largs')
-        endif
-        if a:maximum!=-1
-            call self.nextthrow('@$@largs>'.a:maximum,
-                        \       'toolong',  a:maximum, '@$@largs')
-        endif
-    endif
-    return self.close()
-endfunction
 "▶1 substostr      :: [subscript] → String
 function s:F.substostr(subscripts)
     let r=''
     endfor
     return r
 endfunction
-"▶1 getfunc        :: funccontext[, addarg, ...] + self → String + self
+"▶1 argstr         :: [genString::Bool, [subscript]] + self → String
+"▶2 addargchunk    :: [chunk], chunk::String, literal::Bool → _ + chunks
+function s:F.addargchunk(chunks, chunk, literal)
+    if a:literal==(len(a:chunks)%2)
+        call add(a:chunks, a:chunk)
+    else
+        let a:chunks[-1].=a:chunk
+    endif
+endfunction
+"▲2
+function s:constructor.argstr(...)
+    if get(a:000, 0, 0)
+        let chunks=[self.argbase]
+        for sub in get(a:000, 1, self.subs)
+            let tsub=type(sub)
+            if tsub==type('')
+                if sub=~#'^\w\+$'
+                    call s:F.addargchunk(chunks, '.'.sub, 0)
+                else
+                    call s:F.addargchunk(chunks, '['.s:F.string(sub).']', 0)
+                endif
+            elseif tsub==type(0)
+                call s:F.addargchunk(chunks, '['.sub.']', 0)
+            else
+                if type(sub[0])==type('')
+                    call s:F.addargchunk(chunks, '[', 0)
+                    call s:F.addargchunk(chunks, 'string('.sub[0].')', 1)
+                    if len(sub)>1
+                        call s:F.addargchunk(chunks, ':', 0)
+                        call s:F.addargchunk(chunks, sub[1], 1)
+                    endif
+                    call s:F.addargchunk(chunks, ']', 0)
+                else
+                    call s:F.addargchunk(chunks, '['.join(sub, ':').']', 0)
+                endif
+            endif
+            unlet sub
+        endfor
+        return join(map(chunks, 'v:key%2 ? v:val : string(v:val)'), '.')
+    else
+        return self.argbase.s:F.substostr(get(a:000, 1, self.subs))
+    endif
+endfunction
+"▶1 incsub         :: &self
+function s:constructor.incsub()
+    let self.subs[-1]+=1
+    return self
+endfunction
+"▶1 addthrow       :: msg::String, msgarg, ... + self → self + self
+function s:constructor.addthrow(msg, ...)
+    let args=copy(a:000)
+    let curidx=index(args, '@#@')
+    if curidx!=-1
+        let args=map(args[:(curidx-1)], 'string(v:val)')+[self.argstr(1)]+
+                    \args[(curidx+1):]
+    endif
+    return self.add('call add(@$@messages, ['.string(a:msg).', '.
+                \                             join(args, ', ').'])',
+                \   'throw "CHECKFAILED"').up()
+endfunction
+"▶1 nextthrow      :: condition::expr, throwargs + self → self + self
+function s:constructor.nextthrow(cond, ...)
+    return call(self.nextcond(a:cond).addthrow, a:000, self)
+endfunction
+"▶1 getfunc        :: funccontext[, addarg, ...] + self → String
 function s:constructor.getfunc(func, ...)
     let r=self.getvar(a:func[1]).'('
     let args=[]
     let r.=join(args, ', ').')'
     return r
 endfunction
-"▶1 getvar         :: varcontext + self → String + self
+"▶1 getexpr        :: exprcontext[, curstr] + self → String
+function s:constructor.getexpr(expr, ...)
+    let curargstr=self.argstr()
+    let this=get(a:000, 0, curargstr)
+    return substitute(substitute(a:expr[1],
+                \'\V@.@', escape(this,      '&~\'), 'g'),
+                \'\V@:@', escape(curargstr, '&~\'), 'g')
+endfunction
+"▶1 getvar         :: varcontext + self → String
 function s:constructor.getvar(var)
     if a:var[0] is 'plugvar'
         return '@%@.p.'.a:var[1].s:F.substostr(a:var[2:])
     elseif a:var[0] is 'expr'
-        return substitute(a:var[1], '\V@.@', self.argstr(), 'g')
+        return self.getexpr(a:var)
     elseif a:var[0] is 'string'
         return s:F.string(a:var[1])
     elseif a:var[0] is 'argument'
         return self.argstr()
     endif
 endfunction
-"▶1 getlvarid      :: varname + self → varname + self
+"▶1 getlvarid      :: varname + self → varname
 function s:constructor.getlvarid(v)
     return printf('@$@%s%X', a:v, len(self.stack))
 endfunction
-"▶1 argstr         :: [genString::Bool, [subscript]] + self → String
-"▶2 addargchunk    :: [chunk], chunk::String, literal::Bool → _ + chunks
-function s:F.addargchunk(chunks, chunk, literal)
-    if a:literal==(len(a:chunks)%2)
-        call add(a:chunks, a:chunk)
-    else
-        let a:chunks[-1].=a:chunk
-    endif
-endfunction
-"▲2
-function s:constructor.argstr(...)
-    if get(a:000, 0, 0)
-        let chunks=['@@@']
-        for sub in get(a:000, 1, self.subs)
-            let tsub=type(sub)
-            if tsub==type('')
-                if sub=~#'^\w\+$'
-                    call s:F.addargchunk(chunks, '.'.sub, 0)
-                else
-                    call s:F.addargchunk(chunks, '['.s:F.string(sub).']', 0)
-                endif
-            elseif tsub==type(0)
-                call s:F.addargchunk(chunks, '['.sub.']', 0)
-            else
-                if type(sub[0])==type('')
-                    call s:F.addargchunk(chunks, '[', 0)
-                    call s:F.addargchunk(chunks, sub[0], 1)
-                    if len(sub)>1
-                        call s:F.addargchunk(chunks, ':', 0)
-                        call s:F.addargchunk(chunks, sub[1], 1)
-                    endif
-                    call s:F.addargchunk(chunks, ']', 0)
-                else
-                    call s:F.addargchunk(chunks, '['.join(sub, ':').']', 0)
-                endif
-            endif
-            unlet sub
-        endfor
-        return join(map(chunks, 'v:key%2 ? v:val : string(v:val)'), '.')
-    else
-        return '@@@'.s:F.substostr(get(a:000, 1, self.subs))
-    endif
-endfunction
 "▶1 addtuple       :: tupledesc, idx, defaultArgType + self → self + self
 function s:constructor.addtuple(tuple, idx, default)
     call add(self.subs, 0)
     for arg in a:tuple[1]
         call self.compilearg(arg, a:idx.'(tuple).'.self.subs[-1], a:default)
-        let self.subs[-1]+=1
+        call self.incsub()
     endfor
     call remove(self.subs, -1)
     return self
     call remove(self.subs, -1)
     call self.up().close()
 endfunction
+"▶1 adddict        :: dicdesc, idx, defaultArgType + self → self + self
+function s:constructor.adddict(dic, idx, default)
+    if len(a:dic[1])==1
+        let curargstr=self.argstr()
+        return self.nextthrow('!empty('.curargstr.')',
+                    \         'keynmatch',a:idx,'@#@', 'keys('.curargstr.')[0]')
+                    \.close()
+    endif
+    let hascheck=len(filter(copy(a:dic[1]), 'v:val[0] is "check"'))
+    let keystr=self.getlvarid('key')
+    call self.add('for '.keystr.' in keys('.self.argstr().')').deeper()
+    call add(self.subs, [keystr])
+    if hascheck
+        let foundstr=self.getlvarid('found')
+        let msglenstr=self.getlvarid('msglen')
+        let pmsglenstr=self.getlvarid('pmsglen')
+        call self.add('let '.foundstr.'=0',
+                    \ 'let '.msglenstr. '=len(@$@messages)',
+                    \ 'let '.pmsglenstr.'=len(@$@pmessages)')
+    endif
+    let addedcond=0
+    let addedcheck=0
+    let i=-1
+    for check in a:dic[1][1:]
+        let i+=1
+        if check[0] is 'eq'
+            let addedcond=1
+            call self.nextcond(keystr.' is '.s:F.string(check[1]))
+        elseif check[0] is 'regex'
+            let addedcond=1
+            call self.nextcond(keystr.'=~#'.s:F.string(check[1]))
+        elseif check[0] is 'func'
+            let addedcond=1
+            call self.nextcond(self.getfunc(check[1], keystr))
+        elseif check[0] is 'expr'
+            let addedcond=1
+            call self.nextcond(self.getexpr(check[1], keystr))
+        elseif check[0] is 'any'
+            let addedcond=0
+            call self.nextcond()
+        elseif check[0] is 'check'
+            let savedargbase=self.argbase
+            let self.argbase=keystr
+            let savedsubs=remove(self.subs, 0, -1)
+            if i>0
+                if addedcheck
+                    call self.nextcond('!'.foundstr)
+                else
+                    call self.nextcond()
+                endif
+            elseif addedcond
+                call self.close()
+            endif
+            call self.add('try').deeper()
+                            \.compilearg(check[1], a:idx.'.'.i.'(key)', 'check')
+            let self.argbase=savedargbase
+            let self.subs+=savedsubs
+            call self.add('let '.foundstr.'=1')
+                            \.compilearg(check[2],a:idx.'.'.i.'(val)',a:default)
+                            \.up()
+                        \.add('catch /^CHECKFAILED$/').deeper()
+                            \.nextcond(foundstr)
+                                \.add('throw "CHECKFAILED"')
+                            \.up().close()
+                            \.nextcond('len(@$@messages)>'.msglenstr)
+                                    \.add('call remove(@$@messages, '.
+                                    \                  msglenstr.', -1)')
+                            \.up().close()
+                            \.nextcond('len(@$@pmessages)>'.pmsglenstr)
+                                    \.add('call remove(@$@pmessages, '.
+                                    \                  pmsglenstr.', -1)')
+                            \.up().close()
+                        \.up().close()
+            if i>0
+                call self.up().close()
+            endif
+            let addedcond=0
+            let addedcheck=1
+            continue
+        endif
+        if hascheck
+            call self.add('let '.foundstr.'=1')
+        endif
+        call self.compilearg(check[2], a:idx.'.'.i.'(val)', a:default).up()
+    endfor
+    if hascheck
+        call self.nextcond('!'.foundstr)
+    else
+        call self.nextcond()
+    endif
+    call self.addthrow('keynmatch', a:idx, '@#@', keystr).close().up().close()
+    call remove(self.subs, -1)
+    return self
+endfunction
 "▶1 compilemsg     :: msgcontext, _ + self → self + self
 function s:constructor.compilemsg(msg, idx)
     let msg=[a:msg[1]]
         call self.add('let '.curargstr.'='.self.getfunc(a:pipe[1], curargstr))
     "▶2 `expr' pipe
     elseif a:pipe[1][0] is 'expr'
-        call self.add('let '.curargstr.'='.substitute(a:pipe[1][1],
-                    \                                 '\V@.@', curargstr, 'g'))
+        call self.add('let '.curargstr.'='.self.getexpr(a:pipe[1], curargstr))
     "▶2 Built-in pipes
     elseif a:pipe[1][0] is 'intfunc'
         let desc=a:pipe[1][1:]
             call self.addlist(desc, a:idx, 'pipe')
         "▶3 `dict'
         elseif desc[0] is 'dict'
-            " TODO
+            call self.adddict(desc, a:idx, 'pipe')
         "▶3 `in'
         elseif desc[0] is 'in'
             if len(desc)==2 || (desc[2][1][0] is 'intfunc' &&
         "▶2 `expr' check
         elseif check[0] is 'expr'
             let addedcond=1
-            call self.nextthrow(substitute(check[1], '\V@.@', curargstr, 'g').
-                        \                                           ' is 0',
+            call self.nextthrow(self.getexpr(check, curargstr).' is 0',
                         \       'exprfail', idx, '@#@')
         "▶2 `intfunc' check
         else
             "▶3 `eval'
             elseif desc[0] is 'eval'
                 let addedcond=1
-                call self.nextthrow(substitute(desc[1][1],
-                            \                  '\V@.@', curargstr, 'g').' is 0',
+                call self.nextthrow(self.getexpr(desc[1], curargstr).' is 0',
                             \       'exprfail', idx, '@#@')
             "▶3 `either'
             elseif desc[0] is 'either'
                 call self.addlist(desc, idx, 'check')
             "▶3 `dict'
             elseif desc[0] is 'dict'
-                let addedcond=1
-                call self.addtypecond([type({})], idx)
-                " TODO
+                call self.addtypecond([type({})], idx).close()
+                let addedcond=0
+                call self.adddict(desc, idx, 'check')
             "▶3 `in'
             elseif desc[0] is 'in'
                 let addedcond=1
     endif
     return self
 endfunction
+"▶1 getlenrange    :: adescr → (minlen, maxlen) + adescr
+function s:F.getlenrange(adescr)
+    let minimum=0
+    let maximum=0
+    "▶2 “arg” key
+    if has_key(a:adescr, 'arg')
+        let minimum=len(a:adescr.arg)
+        let maximum=minimum
+    endif
+    "▶2 “optional” key
+    if has_key(a:adescr, 'optional')
+        let lenlist=map(copy(a:adescr.optional), 's:F.getlenrange(v:val)[1]')
+        if index(lenlist, -1)!=-1
+            let maximum=-1
+        else
+            let maximum+=max(lenlist)
+        endif
+    endif
+    "▶2 “actions” key
+    if has_key(a:adescr, 'actions')
+        let minset=0
+        let amin=0
+        let amax=0
+        let has0=0
+        let hasa=0
+        for action in a:adescr.actions
+            if action[0] is 0
+                let has0=1
+            else
+                let hasa=1
+            endif
+            if len(action)>1
+                let [namin, namax]=s:F.getlenrange(action[1])
+                if namin<amin || !minset
+                    let minset=1
+                    let amin=namin
+                endif
+                if namax==-1
+                    let maximum=-1
+                elseif namax>amax
+                    let amax=namax
+                endif
+            endif
+        endfor
+        let minimum+=((has0)?(0):(hasa))+amin
+        if maximum!=-1
+            let maximum+=hasa+amax
+        endif
+    endif
+    "▶2 “prefixes” key
+    if has_key(a:adescr, 'prefixes')
+        let maximum=-1
+        for prefopts in map(copy(a:adescr.prefixes), 'v:val[1]')
+            let minimum+=((prefopts.opt)?
+                        \   (0):
+                        \   ((prefopts.list)?
+                        \       (2):
+                        \       (1+prefopts.argnum)))
+        endfor
+    endif
+    "▶2 “next” key
+    if has_key(a:adescr, 'next')
+        let maximum=-1
+    endif
+    "▲2
+    let a:adescr.minimum=minimum
+    let a:adescr.maximum=maximum
+    return [minimum, maximum]
+endfunction
+"▶1 addlencheck   :: minlen, maxlen + self → self + self
+function s:constructor.addlencheck(minimum, maximum)
+    let largsstr=self.getlvarid('largs')
+    call self.add('let '.largsstr.'=len('.self.argstr(0, self.subs[:-2]).')')
+    if a:maximum==a:minimum
+        call self.nextthrow(largsstr.' isnot '.a:maximum,
+                    \       'invlen', a:minimum, largsstr)
+    else
+        if a:minimum>0
+            call self.nextthrow(largsstr.'<'.a:minimum,
+                        \       'tooshort', a:minimum, largsstr)
+        endif
+        if a:maximum!=-1
+            call self.nextthrow(largsstr.'>'.a:maximum,
+                        \       'toolong',  a:maximum, largsstr)
+        endif
+    endif
+    return self.close()
+endfunction
 "▶1 compileadesc   :: adescr + self → self + self
+" XXX in order for this function to work self.argstr(self.subs[:-2]) should 
+" point to argument being checked and self.subs[-1] should be a number
 function s:constructor.compileadesc(adescr)
     if !has_key(a:adescr, 'minimum')
         call s:F.getlenrange(a:adescr)
     endif
     if !has_key(a:adescr, 'checkedfor')
-        call self.lencheck(self.subs[-1]+a:adescr.minimum,
+        call self.addlencheck(self.subs[-1]+a:adescr.minimum,
                     \      self.subs[-1]+a:adescr.maximum)
         let a:adescr.checkedfor=1
     endif
     if has_key(a:adescr, 'arg')
         for arg in a:adescr.arg
             call self.compilearg(arg, self.subs[-1])
-            let self.subs[-1]+=1
+            call self.incsub()
         endfor
     endif
     " TODO
 "▶1 compilestr     :: String, type → [String]
 function s:F.compilestr(vars, string, type)
     "▶2 Setup self
-    let t   =   { 'tree': s:_r.fwc_parser(a:string, a:type)[1:],
-                \ 'type': a:type,
-                \'ctree': [],
-                \'stack': [],
-                \ 'subs': [0],
-                \    'l': 0,
-                \ 'vars': a:vars,
-                \ 'vids': {},
+    let t   =   {   'tree': s:_r.fwc_parser(a:string, a:type)[1:],
+                \   'type': a:type,
+                \  'ctree': [],
+                \  'stack': [],
+                \   'subs': [0],
+                \      'l': 0,
+                \   'vars': a:vars,
+                \   'vids': {},
+                \'argbase': '@@@'
                 \}
     call extend(t, s:constructor, 'error')
     let t.throw=s:_f.throw
                     \'let @$@pmessages=[]',
                     \'try').deeper()
         if !t.o.only
-            call t.add('let @$@largs=len(@@@)')
             if t.type is 'check' || t.type is 'filter'
                 call t.compileadesc(t.tree)
             else

plugin/frawor/fwc/parser.vim

             \  'invreg': 'invalid regular expression: %s',
             \ 'wordend': 'regular expression cannot end with %s',
             \  'noexpr': 'expected expression, but got nothing',
+            \  'nosubs': 'expected subscript, but got %s'
         \}
 "▶1 s:args
 let s:args={}
     return self
 endfunction
 "▶1 getsubscr  :: &self!
-" Input: ( "." ( "."? ( ":" {n} {n} | {subscript} ) )* )
+" Input: ( "."? ( ":" {n} {n} | {subscript} ) )*
 "        {subscript} :: {str}
 "                     | [0-9] {wordchar}*
 "                     | {wordchar}+
 "                {n} :: "-"? {wordchar}+
 " Output: add((String|Number|context(Number, Number))*)
 function s:parser.getsubscr()
+    let requiresdot=0
     while self.len
         let c=self.readc()
-        if c is '"'
-            call self.add(self.readstr())
-        elseif c is "'"
-            call self.add(self.readsstr())
+        if requiresdot
+            let requiresdot=0
+            if c isnot '.'
+                call self.ungetc(c)
+                break
+            endif
+            continue
         elseif c=~#'^\d'
             call self.add(+c)
         elseif c=~#'^\w'
             call self.add(c)
+        elseif c is '"'
+            call self.add(self.readstr())
+        elseif c is "'"
+            call self.add(self.readsstr())
         elseif c is ':'
             call self.addcon()
             " Start and end subscripts
                 call self.add(+v)
             endfor
             call self.conclose()
-        elseif c isnot '.'
-            call self.ungetc(c)
-            break
+        else
+            call self.throw('nosubs', c)
         endif
+        let requiresdot=1
     endwhile
     return self
 endfunction
 " Input: "{" ({keydescr} {arg})* "}"
 "        {keydescr} :: {str}
 "                    | "/" {reg}(endstr=/)
-"                    | {arg}
-" Output: context(ddescr, [{keycon}])
+"                    | "?" {arg}
+"                    | "*" {func}
+"                    | "=" {expr}
+"                    | "-"
+"                    | {wordchar}+
+" Output: context(ddescr, {keycon}*)
 "         {keycon} :: context(eq,    String, {arg})
 "                   | context(regex, String, {arg})
 "                   | context(check, {arg},  {arg})
+"                   | context(func,  {func}, {arg})
+"                   | context(expr,  {expr}, {arg})
+"                   | context(any,           {arg})
 function s:parser.getddescr()
     call self.addcon('ddescr')
     if self.len
             let c=self.readc()
             if c is '}'
                 break
+            elseif c is '/'
+                call self.addcon('regex', self.readreg('/')).scan().conclose()
+            elseif c is '*'
+                call self.addcon('func').getfunc().scan().conclose()
+            elseif c is '='
+                call self.addcon('expr').getexpr().scan().conclose()
+            elseif c is '?'
+                call self.addcon('check').scan().scan().conclose()
+            elseif c is '-'
+                call self.addcon('any').scan().conclose()
             elseif c is '"'
                 call self.addcon('eq', self.readstr()).scan().conclose()
             elseif c is "'"
                 call self.addcon('eq', self.readsstr()).scan().conclose()
-            elseif c is '/'
-                call self.addcon('regex', self.readreg('/')).scan().conclose()
-            else
-                call self.addcon('check').ungetc(c).scan().scan().conclose()
+            elseif c=~#'^\w'
+                call self.addcon('eq', c).scan().conclose()
             endif
         endwhile
     endif
     return self.addcon('expr', self.readexpr()).conclose()
 endfunction
 "▶1 getchvar   :: &self
-" Input: ( ( "^"* ) | ( ">" | "<" )* ) {subscr}?
+" Input: ( ( "^"* ) | ( ">" | "<" )* ) ( "." {subscr} )?
 " Output: context(argument, Number, {subscr}?)
 "       | context(cur, UInt, {subscr}?)
 function s:parser.getchvar()
 function s:parser.getvar()
     let c=self.readc()
     if c=~#'^\w'
-        call self.addcon('plugvar', c).getsubscr().conclose()
+        call self.addcon('plugvar').ungetc(c).getsubscr().conclose()
     elseif c is '@'
         return self.getchvar()
     elseif c is '='
 plugin/frawor/fwc/compiler:runfail
 plugin/frawor/fwc/compiler:evalfail
 plugin/frawor/fwc/compiler:nsfunc
+::: Section <Pipes/Built-in pipes/dict>
+plugin/frawor/fwc/compiler:keynmatch
+plugin/frawor/fwc/compiler:keynmatch
+plugin/frawor/fwc/compiler:nreg
+plugin/frawor/fwc/compiler:keynmatch
+plugin/frawor/fwc/compiler:keynmatch
+plugin/frawor/fwc/compiler:keynmatch
+::: Section <Pipes/Built-in pipes/tuple>
 plugin/frawor/fwc/compiler:invlstlen
 plugin/frawor/fwc/compiler:typefail
 ::: Section <Pipes/Built-in pipes/list>
 plugin/frawor/fwc/compiler:typefail
 plugin/frawor/fwc/compiler:ninlist
 plugin/frawor/fwc/compiler:ninlist
+::: Section <Checks/Built-in checks/dict>
+plugin/frawor/fwc/compiler:keynmatch
+plugin/frawor/fwc/compiler:typefail
+plugin/frawor/fwc/compiler:nbool
+plugin/frawor/fwc/compiler:nbool
+plugin/frawor/fwc/compiler:keynmatch
+plugin/frawor/fwc/compiler:keynmatch
+plugin/frawor/fwc/compiler:nbool
+plugin/frawor/fwc/compiler:keynmatch
+plugin/frawor/fwc/compiler:nbool
+plugin/frawor/fwc/compiler:keynmatch
 ::: Section <Checks/Built-in checks/path>
 plugin/frawor/fwc/compiler:nwrite
 plugin/frawor/fwc/compiler:nowrite

test/fwctests.dat

 ['earg',                   'filter'], ['('],                0
 ['isfunc',                 'filter'], [0],                  0
 ['isfunc',                 'filter'], [function('tr')],     [function("tr")]
+#▶3 dict
+['dict {abc bool}',        'filter'], [{'abc': {}}],        [{'abc': 0}]
+['dict {abc bool}',        'filter'], [{'ab': {}}],         0
+['dict {/^a/ bool}',       'filter'], [{'a': 2, 'ab': {}}], [{'a': 1, 'ab': 0}]
+['dict {/^a/ bool}',       'filter'], [{'a': 2, 'Ab': {}}], 0
+['dict {a isreg}',         'filter'], [{'a': '\v('}],       0
+['dict {?isreg bool}',     'filter'], [{'abc': {}}],        [{'abc': 0}]
+['dict {?isreg bool}',     'filter'], [{'\v(': {}}],        0
+['dict {?isreg bool ?_ _}','filter'], [{'a': 4, '\v(': 2}], [{'a': 1, '\v(': 2}]
+['dict {a _ ?isreg bool}', 'filter'], [{'a': 4, 'ab':  2}], [{'a': 4, 'ab':  1}]
+['dict {*$"exists" bool}', 'filter'], [{'g:': []}],         [{'g:': 0}]
+['dict {*$"exists" bool}', 'filter'], [{'g:+': []}],        0
+['dict {=exists(@.@)bool}','filter'], [{'g:': []}],         [{'g:': 0}]
+['dict {=exists(@.@)bool}','filter'], [{'g:+': []}],        0
+#▶3 tuple
 ['tuple bool, bool',       'filter'], [['', 'cba']],        [[0, 1]]
 ['tuple bool, bool',       'filter'], [['abc']],            0
 ['tuple bool, bool',       'filter'], ['abc'],              0
 ['tuple (?bool |=(!@.@))', 'filter'], [[1]],                [[0]]
+['tuple (tuple bool tuple bool, bool)', 'filter'], [[[''],[4,0]]], [[[0],[1,0]]]
 #▶3 list
 ['list bool',              'filter'], ["['abc', 'def']"],   0
 ['list bool',              'filter'], [['abc', 'def']],     [[1, 1]]
 ['bool',            'check'], [1],                  1
 ['bool',            'check'], [2],                  0
 ['isreg',           'check'], [0],                  0
-['isreg',           'check'], ['\(abc\)'],          1
-['isreg',           'check'], ['\(abc'],            0
+['isreg',           'check'], ['\v(abc)'],          1
+['isreg',           'check'], ['\v(abc'],           0
 ['key={"abc": 1}',  'check'], ['abc'],              1
 ['key={"abc": 1}',  'check'], ['ab'],               0
 ['key={"abc": 1}',  'check'], [0],                  0
 ['in =["a",0]',     'check'], [0],                  1
 ['_',               'check'], [[[[]]]],             1
 ['any',             'check'], [[[[]]]],             1
+#▶3 dict
+['dict {}',                   'check'], [{}],              1
+['dict {}',                   'check'], [{'a': 1}],        0
+['dict {}',                   'check'], [[]],              0
+['dict {abc bool}',           'check'], [{'abc': 1}],      1
+['dict {abc bool}',           'check'], [{'abc': 2}],      0
+['dict {/^a/ bool}',          'check'], [{'a':1, 'ab':0}], 1
+['dict {/^a/ bool}',          'check'], [{'a':1, 'ab':2}], 0
+['dict {/^a/ bool}',          'check'], [{'a':1, 'Ab':2}], 0
+['dict {*ordered bool}',      'check'], [{'abc': 1}],      1
+['dict {*ordered(.) bool}',   'check'], [{'abc': 1}],      1
+['dict {*ordered bool}',      'check'], [{'bac': 1}],      0
+['dict {=exists(@.@) bool}',  'check'], [{'g:': 0}],       1
+['dict {=exists(@.@) bool}',  'check'], [{'g:': 2}],       0
+['dict {=exists(@.@) bool}',  'check'], [{'g:+': 1}],      0
+['dict {?|earg bool bool}',   'check'], [{'0': 1}],        1
+['dict {?|earg bool bool}',   'check'], [{'0': 2}],        0
+['dict {?|earg bool bool}',   'check'], [{'2': 1}],        0
 #▶3 path
 :call os.mkdir('FWCdirectory')
 :call os.chdir('FWCdirectory')