Commits

ZyX_I committed b539e6a

@/fwc: Added `take' pipe, added matcher support to `in' and `key' pipes

  • Participants
  • Parent commits 283b0c2

Comments (0)

Files changed (4)

File plugin/frawor/fwc/compiler.vim

             \    'nsfunc': 'expected function name or '.
             \              'callable function reference, but got %s',
             \      'nreg': "string `%s' is not a valid regular expression (%s)",
-            \    'nmatch': "string `%s' does not match regular expression `%s'",
+            \ 'nregmatch': "string `%s' does not match regular expression `%s'",
             \   'keysmis': 'the following required keys are missing: %s',
             \    'keymis': 'key `%s'' is missing',
             \   'runfail': 'running argument failed with exception %s',
             \     'nread': '%s is not readable',
             \ 'nexecable': '%s is not executable',
             \ 'keynmatch': 'key `%s'' does not match any specification',
+            \    'nmatch': 'no matches found for %s',
         \}, '"Error while processing check %s for %s: ".v:val'))
 let s:_messages._types=['number', 'string', 'function reference', 'list',
             \           'dictionary']
     call add(s:_messages._types, 'float')
 endif
 "▶1 Matchers
+" {m}(args): ld::(List|Dict), str::String, [args], acceptfirst::(0|1) → str
+"          | ld::(List|Dict), str::String, [args], 2 → [String]
+let s:F.matchers={}
+"▶2 func  :: {m}(Function, args::List)
+function s:F.matchers.func(ld, str, Func, args, acceptfirst)
+    let result=call(a:Func, a:args+[a:ld, a:str], {})
+    if a:acceptfirst is 2
+        return result
+    elseif a:acceptfirst || len(result)==1
+        return result[0]
+    else
+        return 0
+    endif
+endfunction
+"▶2 exact :: {m}(ignorecase::Bool)
+function s:F.matchers.exact(ld, str, ignorecase, acceptfirst)
+    if type(a:ld)==type({})
+        if a:ignorecase
+            let list=sort(keys(a:ld))
+        else
+            return ((has_key(a:ld, a:str)==-1)?(0):(a:str))
+        endif
+    elseif !a:ignorecase
+        return ((index(a:ld, a:str)==-1)?(0):(a:str))
+    else
+        let list=a:ld
+    endif
+    let idx=index(list, a:str, 0, 1)
+    if idx==-1
+        return 0
+    elseif a:acceptfirst is 2
+        let r=[list[idx]]
+        while 1
+            let idx=index(list, a:str, idx+1, 1)
+            if idx==-1
+                break
+            else
+                call add(r, list[idx])
+            endif
+        endwhile
+        return r
+    elseif a:acceptfirst
+        return list[idx]
+    else
+        return ((index(list, a:str, idx+1, 1)==-1)?(list[idx]):(0))
+    endif
+endfunction
+"▶2 start :: {m}(ignorecase::Bool)
+function s:F.matchers.start(ld, str, ignorecase, acceptfirst)
+    if type(a:ld)==type({})
+        let list=sort(keys(a:ld))
+    else
+        let list=a:ld
+    endif
+    let r=[]
+    let lstr=len(a:str)-1
+    for value in list
+        if type(value)==type('') && ((a:ignorecase)?(value[:(lstr)]==?a:str):
+                    \                               (value[:(lstr)]==#a:str))
+            if a:acceptfirst is 1
+                return value
+            elseif empty(r)
+                call add(r, value)
+            else
+                return 0
+            endif
+        endif
+        unlet value
+    endfor
+    return ((a:acceptfirst is 2)?(r):(get(r, 0, 0)))
+endfunction
+"▶2 smart :: {m}()
+let s:smartfilters=[
+            \'v:val is a:str',
+            \'v:val==?a:str',
+            \'v:val=~#''\V\^''.estr',
+            \'v:val=~?''\V\^''.estr',
+            \'stridx(v:val, a:str)!=-1',
+            \'v:val=~?"\\V".estr',
+            \'v:val=~#reg',
+            \'v:val=~?reg',
+            \'v:val=~#reg2',
+            \'v:val=~?reg2',
+            \]
+function s:F.matchers.smart(ld, str, acceptfirst)
+    let estr=escape(a:str, '\')
+    let reg='\V'.join(split(estr, '\v[[:punct:]]@<=|[[:punct:]]@='), '\.\*')
+    let reg2='\V'.join(map(split(a:str,'\v\_.@='),'escape(v:val,"\\")'), '\.\*')
+    let list=filter(((type(a:ld)==type({})?(sort(keys(a:ld))):(copy(a:ld)))),
+                \   'type(v:val)=='.type(''))
+    for filter in s:smartfilters
+        let r=filter(copy(list), filter)
+        if !empty(r)
+            if a:acceptfirst is 2
+                return r
+            elseif a:acceptfirst || len(r)==1
+                return r[0]
+            else
+                return 0
+            endif
+        endif
+    endfor
+    return 0
+endfunction
 "▶1 string         :: String → String
 function s:F.string(str)
     return substitute(substitute(substitute(substitute(string(a:str),
 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]).'('
+"▶1 getfunc        :: funccontext, split[, addarg, ...] + self → String
+function s:constructor.getfunc(func, split, ...)
+    if a:split
+        let r=[self.getvar(a:func[1])]
+    else
+        let r=self.getvar(a:func[1]).'('
+    endif
     let args=[]
     let added000=0
     for arg in a:func[2:]
     if a:0 && !added000
         let args+=a:000
     endif
-    let r.=join(args, ', ').')'
+    if a:split
+        let r+=args
+    else
+        let r.=join(args, ', ').')'
+    endif
+    return r
+endfunction
+"▶1 getmatcher     :: matchercontext, ldstr, strstr + self → String
+function s:constructor.getmatcher(matcher, ldstr, strstr)
+    let r='@%@.F.matchers.'.a:matcher[1][1].'('.a:ldstr.', '.a:strstr
+    if len(a:matcher[1])>3
+        for arg in a:matcher[1][2:-2]
+            if type(arg)==type([]) && arg[0] is 'func'
+                let [func; args]=self.getfunc(arg)
+                let r.=', '.func.', ['.join(args, ', ').']'
+            else
+                let r.=', '.string(arg)
+            endif
+        endfor
+    endif
+    if self.type is 'check' || self.type is 'filter'
+        let r.=', '.a:matcher[1][-1].')'
+    else
+        let r.=', 2)'
+    endif
     return r
 endfunction
 "▶1 getexpr        :: exprcontext[, curstr] + self → String
     elseif a:var[0] is 'evaluate'
         return eval(substitute(self.getvar(a:var[1]), '@%@', 'self.vars', 'g'))
     elseif a:var[0] is 'func'
-        return self.getfunc(a:var)
+        return self.getfunc(a:var, 0)
     elseif a:var[0] is 'this'
         return self.argstr()
     endif
             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))
+            call self.nextcond(self.getfunc(check[1], 0, keystr))
         elseif check[0] is 'expr'
             let addedcond=1
             call self.nextcond(self.getexpr(check[1], keystr))
     let curargstr=self.argstr()
     "▶2 `func' pipe
     if a:pipe[1][0] is 'func'
-        call self.add('let '.curargstr.'='.self.getfunc(a:pipe[1], curargstr))
+        call self.add('let '.curargstr.'='.self.getfunc(a:pipe[1],0, curargstr))
     "▶2 `expr' pipe
     elseif a:pipe[1][0] is 'expr'
         call self.add('let '.curargstr.'='.self.getexpr(a:pipe[1], curargstr))
         "▶3 `dict'
         elseif desc[0] is 'dict'
             call self.adddict(desc, a:idx, 'pipe')
-        "▶3 `in'
-        elseif desc[0] is 'in'
+        "▶3 `in', `key'
+        elseif desc[0] is 'in' || desc[0] is 'key'
             if len(desc)==2 || (desc[2][1][0] is 'intfunc' &&
                         \       desc[2][1][1] is 'exact' &&
                         \       desc[2][1][2] is 0)
                 call self.compilecheck(['check', a:pipe[1]], a:idx)
             else
-                call self.addtypecond([type('')], idx)
-                " TODO
-            endif
-        "▶3 `key'
-        elseif desc[0] is 'key'
-            if len(desc)==2 || (desc[2][1][0] is 'intfunc' &&
-                        \       desc[2][1][1] is 'exact' &&
-                        \       desc[2][1][2] is 0)
-                call self.compilecheck(['check', a:pipe[1]], a:idx)
-            else
-                call self.addtypecond([type('')], idx)
-                " TODO
+                call self.addtypecond([type('')], a:idx).close()
+                let matchstr=self.getlvarid('match')
+                call self.add('let '.matchstr.'='.
+                            \              self.getmatcher(desc[2],
+                            \                              self.getvar(desc[1]),
+                            \                              curargstr))
+                            \.nextthrow(matchstr.' is 0',
+                            \           'nmatch', a:idx,'@#@',curargstr).close()
+                            \.add('let '.curargstr.'='.matchstr)
             endif
         "▶3 `take'
         elseif desc[0] is 'take'
-            " TODO
+            call self.addtypecond([type('')], a:idx).close()
+            let varstr=self.getvar(desc[1])
+            let matchstr=self.getlvarid('match')
+            call self.add('let '.matchstr.'='.self.getmatcher(desc[2], varstr,
+                        \                                     curargstr))
+                        \.nextthrow(matchstr.' is 0',
+                        \           'nmatch', a:idx, '@#@', curargstr).close()
+                        \.add('let '.curargstr.'='.varstr.'['.matchstr.']')
         "▶3 `substitute'
         elseif desc[0] is 'substitute'
             call self.add('let '.curargstr.'=substitute('.curargstr.', '.
         "▶2 `func' check
         if check[0] is 'func'
             let addedcond=1
-            call self.nextthrow(self.getfunc(check, curargstr).' is 0',
+            call self.nextthrow(self.getfunc(check, 0, curargstr).' is 0',
                         \       'funcfail', idx, '@#@')
         "▶2 `expr' check
         elseif check[0] is 'expr'
             "▶3 `func'
             if desc[0] is 'func'
                 let addedcond=1
-                call self.nextthrow(self.getfunc(desc[1], curargstr).' is 0',
+                call self.nextthrow(self.getfunc(desc[1], 0, curargstr).' is 0',
                             \       'funcfail', idx, '@#@')
             "▶3 `eval'
             elseif desc[0] is 'eval'
                             \(s:F.string(desc[1][1])):
                             \(self.getvar(desc[1][1])))
                 call self.nextthrow(curargstr.'!~#'.regex,
-                            \       'nmatch', idx, '@#@', curargstr, regex)
+                            \       'nregmatch', idx, '@#@', curargstr, regex)
             "▶3 `path'
             elseif desc[0] is 'path'
                 let addedcond=1
     let id=printf('%s%X', a:type, s:lastid)
     let s:lastid+=1
     let s:vars[id]={
-                \ 'F': {'warn': s:_f.warn},
+                \ 'F': {'warn': s:_f.warn, 'matchers': s:F.matchers},
                 \ 'p': a:plugdict.g,
                 \ 'm': {'types': s:_messages._types},
                 \'os': s:_r.os,

File plugin/frawor/fwc/parser.vim

 "▶3 pipe.take
 " Replaces {argument} with value of the first key from {var}::Dictionary that 
 " matches {argument}
-let s:args.pipe.take=['var', '?omtchr']
+let s:args.pipe.take=['var', 'matcher']
 "▶3 pipe.substitute
 " Runs substitute on {argument}
 let s:args.pipe.substitute=['reg', 'string', 'string']
 "▶2 matcher
 let s:args.matcher={}
 "▶3 matcher.func
-" Uses some other function as matcher
-let s:args.matcher.func=['func']
+" Uses some other function as matcher. Function must accept list or dictionary 
+" and return a list of strings. Additional argument determines what should be 
+" done if function returns list with more then one item
+let s:args.matcher.func=['func', '?one']
 "▶3 matcher.exact
-" Requires exact match. Additional argument determines whether match should be 
-" done case-insensitively
-let s:args.matcher.exact=['?one']
+" Requires exact match. First additional argument determines whether match 
+" should be done case-insensitively, second determines whether ambigious matches 
+" should be accepted (only used if first argument is true)
+let s:args.matcher.exact=['?one', '?one']
 "▶3 matcher.start
 " Requires match at the start of string. First optional arguments determines 
 " case sensitivity, second determines whether ambigious matches should be 

File test/fwccheck.ok

 plugin/frawor/fwc/compiler:runfail
 plugin/frawor/fwc/compiler:evalfail
 plugin/frawor/fwc/compiler:nsfunc
+plugin/frawor/fwc/compiler:nmatch
+plugin/frawor/fwc/compiler:typefail
 ::: Section <Pipes/Built-in pipes/dict>
 plugin/frawor/fwc/compiler:keynmatch
 plugin/frawor/fwc/compiler:keynmatch
 ::: Section <Pipes/Built-in pipes/in>
 plugin/frawor/fwc/compiler:ninlist
 plugin/frawor/fwc/compiler:ninlist
+plugin/frawor/fwc/compiler:nmatch
+plugin/frawor/fwc/compiler:nmatch
+plugin/frawor/fwc/compiler:nmatch
+plugin/frawor/fwc/compiler:nmatch
 ::: Section <Pipes/Built-in pipes/substitute>
 plugin/frawor/fwc/compiler:typefail
 ::: Section <Pipes/Built-in pipes/bool>
 ::: Section <Checks/Built-in checks/list>
 plugin/frawor/fwc/compiler:typefail
 plugin/frawor/fwc/compiler:nreg
-plugin/frawor/fwc/compiler:nmatch
+plugin/frawor/fwc/compiler:nregmatch
 ::: Section <Checks/Built-in checks/tuple>
 plugin/frawor/fwc/compiler:invlstlen
-plugin/frawor/fwc/compiler:nmatch
+plugin/frawor/fwc/compiler:nregmatch
 plugin/frawor/fwc/compiler:nreg
 plugin/frawor/fwc/compiler:nreg
 plugin/frawor/fwc/compiler:invlstlen
 plugin/frawor/fwc/compiler:nsfunc
 plugin/frawor/fwc/compiler:nsfunc
 ::: Section <Checks/Built-in checks/match>
-plugin/frawor/fwc/compiler:nmatch
+plugin/frawor/fwc/compiler:nregmatch
 plugin/frawor/fwc/compiler:typefail
-plugin/frawor/fwc/compiler:nmatch
+plugin/frawor/fwc/compiler:nregmatch
 ::: Section <Checks/Built-in checks/type>
 plugin/frawor/fwc/compiler:typefail
 plugin/frawor/fwc/compiler:typefail

File test/fwctests.dat

 ['earg',                   'filter'], ['('],                0
 ['isfunc',                 'filter'], [0],                  0
 ['isfunc',                 'filter'], [function('tr')],     [function("tr")]
+['take ={"abc": 1} start', 'filter'], ['a'],                [1]
+['take ={"abc": 1} start', 'filter'], ['b'],                0
+['take ={"abc": 1} start', 'filter'], [0],                  0
 #▶3 dict
 ['dict {abc bool}',        'filter'], [{'abc': {}}],        [{'abc': 0}]
 ['dict {abc bool}',        'filter'], [{'ab': {}}],         0
 ['key={"abc": 1}',         'filter'], ['abc'],              ['abc']
 ['key={"abc": 1}',         'filter'], ['ab'],               0
 ['key={"abc": 1}',         'filter'], [0],                  0
+['key={"abc": 1}~start',   'filter'], ['a'],                ['abc']
 #▶3 in
 ['in [abc def]',           'filter'], ['abc'],              ['abc']
 ['in =["a","b"]',          'filter'], ['abc'],              0
 ['in =["a",0]',            'filter'], ['0'],                0
 ['in =["a",0]',            'filter'], [0],                  [0]
 ['in =["a",0]~exact 0',    'filter'], [0],                  [0]
+:let s:list=['abc', 'def', 'abc-def', 'Abc', 'dEf', 'aBc-deF', 'Ghi']
+['in list ~exact',         'filter'], ['abc'],              ['abc']
+['in list ~exact 1',       'filter'], ['ghi'],              ['Ghi']
+['in list ~exact 1',       'filter'], ['gh'],               0
+['in list ~start',         'filter'], ['gh'],               0
+['in list ~start',         'filter'], ['ab'],               0
+['in list ~start 0 1',     'filter'], ['ab'],               ['abc']
+['in list ~start 1',       'filter'], ['gh'],               ['Ghi']
+['in list ~smart',         'filter'], ['a-d'],              0
+['in list ~smart',         'filter'], ['b-d'],              ['abc-def']
+['in list ~smart 1',       'filter'], ['a-d'],              ['abc-def']
 #▶3 substitute
 :let s:regex='\v.'
 :let s:replacement='def'