Commits

ZyX_I committed 515b91a

@/fwc: Implemented some simple built-in checks and filters:
filters: substitute, func, eval, run, earg, bool
checks: func, eval, type, bool, isfunc, isreg, match, _, any
Changes to @/fwc/parser:
- Renamed `list' pipe to `in', added `in' check, renamed `keyof' check
to `key'
- Added getomtchr() function (replaces getmatcher() function in some
positions)
- Improved type=`arg' handling in intfunc()
- Made getstring() add empty string when no characters left
- Added `recursive' option

Comments (0)

Files changed (5)

plugin/frawor/fwc/compiler.vim

 let s:constructor={}
 "▶1 Messages
 let s:_messages={
+            \'notypes' : 'Expected at least one type specification',
             \'tooshort': 'Argument list is too short: '.
             \            'expected at least %u, but got %u',
             \'toolong' : 'Argument list is too long: '.
             \'invlen'  : 'Invalid arguments length: expected %u, but got %u',
         \}
 call extend(s:_messages, map({
-            \'funcfail': 'custom function returned 0',
-            \'exprfail': 'custom expression returned 0',
+            \ 'funcfail': 'custom function returned 0',
+            \ 'exprfail': 'custom expression returned 0',
+            \'typesfail': 'invalid type: expected one of %s, but got %s',
+            \ 'typefail': 'invalid type: expected %s, but got %s',
+            \    'nbool': 'invalid value: expected either 0 or 1, but got %s',
+            \    'nfunc': 'function %s is not callable',
+            \   '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'",
         \}, '"Error while processing check %u for %s: ".v:val'))
+let s:_messages._types=['number', 'string', 'function reference', 'list',
+            \           'dictionary']
+if has('float')
+    call add(s:_messages._types, 'float')
+endif
 "▶1 string         :: String → String
 function s:F.string(str)
     return substitute(substitute(substitute(string(a:str),
     endfor
     return self
 endfunction
+"▶1 compilestring  :: {string} + self → self + self
+function s:constructor.compilestring(str)
+    if type(a:str)==type("")
+        return s:F.string(a:str)
+    endif
+    return self.getvar(a:str)
+endfunction
 "▶1 compilepipe    :: pipecontext, _ + self → self + self
 function s:constructor.compilepipe(pipe, idx)
     let curargstr=self.argstr()
         call self.add('let '.curargstr.'='.
                     \       substitute(a:pipe[1][1],
                     \                  '\V@.@', curargstr, 'g'))
-    else
-        " TODO
+    elseif a:pipe[1][0] is 'intfunc'
+        let desc=a:pipe[1][1:]
+        if desc[0] is 'substitute'
+            call self.add('let '.curargstr.'=substitute('.curargstr.', '.
+                        \((type(desc[1][1])==type(""))?
+                        \       (s:F.string(desc[1][1])):
+                        \       (self.getvar(desc[1][1]))).', '.
+                        \join(map(desc[2:], 'self.compilestring(v:val)'), ', ').
+                        \')')
+        elseif desc[0] is 'in'
+            " TODO
+        elseif desc[0] is 'key'
+            " TODO
+        elseif desc[0] is 'take'
+            " TODO
+        elseif desc[0] is 'func' || desc[0] is 'eval'
+            call self.compilepipe(desc, a:idx)
+        elseif desc[0] is 'run'
+            call self.add('let '.curargstr.'=call('.curargstr.', '.
+                        \                         self.getvar(desc[1]).', {})')
+        elseif desc[0] is 'earg'
+            call self.add('let '.curargstr.'=eval('.curargstr.')')
+        elseif desc[0] is 'bool'
+            call self.add('let '.curargstr.'=!empty('.curargstr.')')
+        else
+            call self.compilecheck(['check', a:pipe[1]], a:idx)
+        endif
     endif
     return self
 endfunction
 let s:constructor.compilefilter=s:constructor.compilepipe
+"▶1 addtypecond    :: types, idx + self → self + self
+function s:constructor.addtypecond(types, idx)
+    let curargstr=self.argstr()
+    let typenames=map(copy(a:types), 's:_messages._types[v:val]')
+    if len(a:types)>=2
+        call self.nextcond('index('.string(a:types).', '.
+                    \            'type('.curargstr.'))==-1')
+                    \.addthrow('typesfail', a:idx, '@#@',
+                    \          string(join(typenames, '/')),
+                    \          '@%@.m.types[type('.curargstr.')]')
+                    \.up()
+    elseif len(a:types)==1
+        call self.nextcond('type('.curargstr.')!='.a:types[0])
+                    \.addthrow('typefail', a:idx, '@#@',
+                    \          string(typenames[0]),
+                    \          '@%@.m.types[type('.curargstr.')]')
+                    \.up()
+    else
+        call self.throw('notypes')
+    endif
+    return self
+endfunction
 "▶1 compilecheck   :: checkcontext, idx + self → self + self
 function s:constructor.compilecheck(check, idx)
     let curargstr=self.argstr()
                         \.addthrow('exprfail', idx, '@#@')
                         \.up()
         else
-            " TODO
+            let desc=check[1:]
+            if desc[0] is 'func'
+                let addedcond=1
+                call self.nextcond(self.getfunc(desc[1], curargstr).' is 0')
+                            \.addthrow('funcfail', idx, '@#@')
+                            \.up()
+            elseif desc[0] is 'eval'
+                let addedcond=1
+                call self.nextcond(substitute(desc[1][1],'\V@.@',curargstr,'g').
+                            \      ' is 0')
+                            \.addthrow('exprfail', idx, '@#@')
+                            \.up()
+            elseif desc[0] is 'type'
+                let addedcond=1
+                call self.addtypecond(desc[1], idx)
+            elseif desc[0] is 'bool'
+                let addedcond=1
+                call self.nextcond('index([0, 1], '.curargstr.')==-1')
+                            \.addthrow('nbool', idx, '@#@',
+                            \                   'string('.curargstr.')')
+                            \.up()
+            elseif desc[0] is 'range'
+                " TODO
+            elseif desc[0] is 'key'
+                " TODO
+            elseif desc[0] is 'hkey'
+                " TODO
+            elseif desc[0] is 'isfunc'
+                let addedcond=1
+                if desc[1]
+                    call self.addtypecond([2], idx)
+                    call self.nextcond('!exists('.string('*'.curargstr).')')
+                                \.addthrow('nfunc', idx, '@#@',
+                                \          'string('.curargstr.')[10:-3]')
+                                \.up()
+                else
+                    call self.nextcond('!((type('.curargstr.')==2 && '.
+                                \         'exists('.string('*'.curargstr).')) '.
+                                \        '|| (type('.curargstr.')=='.type("").
+                                \            '&& exists("*".'.curargstr.')))')
+                                \.addthrow('nsfunc', idx, '@#@',
+                                \                    'string('.curargstr.')')
+                                \.up()
+                endif
+            elseif desc[0] is 'isreg'
+                call self.addtypecond([type('')], idx)
+                call self.close()
+                let addedcond=0
+                call self.add('try')
+                          \.deeper('call matchstr("", '.curargstr.')').up()
+                        \.add('catch /.*/').deeper()
+                          \.addthrow('nreg', idx, '@#@', curargstr,
+                          \                  'v:exception')
+                        \.up().close()
+            elseif desc[0] is 'match'
+                let addedcond=1
+                call self.addtypecond([type('')], idx)
+                let regex=((type(desc[1][1])==type(""))?
+                            \(s:F.string(desc[1][1])):
+                            \(self.getvar(desc[1][1])))
+                call self.nextcond(curargstr.'!~#'.regex)
+                            \.addthrow('nmatch', idx, '@#@', curargstr, regex)
+                            \.up()
+            elseif desc[0] is 'dict'
+                " TODO
+            elseif desc[0] is 'tuple'
+                " TODO
+            elseif desc[0] is 'either'
+                " TODO
+            elseif desc[0] is 'path'
+                " TODO
+            elseif desc[0] is '_' || desc[0] is 'any'
+                " Just do nothing here
+            endif
         endif
         let idx+=1
     endfor
     return self['compile'.self.type]([self.type, a:intfunc], a:idx)
 endfunction
 "▶1 compileadesc   :: adescr + self → self + self
+let s:pipechecks={
+            \'substitute': ['intfunc', 'type',   [type('')]],
+            \        'in': ['intfunc', 'type',   [type('')]],
+            \       'key': ['intfunc', 'type',   [type('')]],
+            \      'take': ['intfunc', 'type',   [type('')]],
+            \       'run': ['intfunc', 'isfunc', 0         ],
+            \      'earg': ['intfunc', 'type',   [type('')]],
+        \}
 function s:constructor.compileadesc(adescr)
     if !has_key(a:adescr, 'minimum')
         call s:F.getlenrange(a:adescr)
         for arg in a:adescr.arg
             let msg=[]
             let i=0
-            let previscheck=0
+            let squashedlen=0
             for proc in arg
                 if proc[0] is 'check'
-                    let previscheck=1
+                    let squashedlen+=1
                     call add(check, proc[1])
                     let i+=1
                     continue
-                elseif previscheck
-                    let previscheck=0
-                    call self.compilecheck(check, i+1-len(check))
+                elseif proc[0] is 'pipe' && proc[1][0] is 'intfunc'
+                            \&& has_key(s:pipechecks, proc[1][1])
+                    call add(check, s:pipechecks[proc[1][1]])
+                elseif self.type is 'filter' && proc[0] is 'intfunc'
+                            \&& has_key(s:pipechecks, proc[1])
+                    call add(check, s:pipechecks[proc[1]])
+                endif
+                if len(check)>1
+                    call self.compilecheck(check, i+((squashedlen)?
+                                \                       (1-squashedlen):
+                                \                       (0)))
+                    let squashedlen=0
                     call remove(check, 1, -1)
                 endif
                 call call(self["compile".proc[0]], [proc, i], self)
                 let i+=1
             endfor
-            if previscheck
+            if len(check)>1
                 call self.compilecheck(check, i+1-len(check))
                 call remove(check, 1, -1)
             endif
     let s:vars[id]={
                 \'F': {'warn': s:_f.warn},
                 \'p': a:plugdict.g,
+                \'m': {'types': s:_messages._types},
             \}
     call add(a:fdict.ids, id)
     execute "function d.f(args)\n    ".
             \                      'unload': s:F.delids,
             \                        'init': {'ids': []}})
 "▶1
+" TODO implement recursion protection
 call frawor#Lockvar(s:, 'lastid,vars')
 " vim: fmr=▶,▲ sw=4 ts=4 sts=4 et tw=80

plugin/frawor/fwc/parser.vim

 "▶3 pipe.substitute
 " Runs substitute on {argument}
 let s:args.pipe.substitute=['reg', 'string', 'string']
-"▶3 pipe.list
+"▶3 pipe.in
 " Picks up first element from {var}::List that matches {argument}
-let s:args.pipe.list=['var', 'matcher']
+let s:args.pipe.in=['var', '?omtchr']
 "▶3 pipe.key
 " Picks up first key from {var}::Dictionary that matches {argument}
-let s:args.pipe.key=['var']
+let s:args.pipe.key=['var', '?omtchr']
 "▶3 pipe.take
 " Replaces {argument} with value of the first key from {var}::Dictionary that 
 " matches {argument}
-let s:args.pipe.take=['var', 'matcher']
+let s:args.pipe.take=['var', '?omtchr']
 "▶3 pipe.func
 " Replaces {argument} with the result of running {func}({argument})
 let s:args.pipe.func=['func']
 "▶3 check.range
 " Checks whether {argument} is in given range
 let s:args.check.range=['number', 'number']
-"▶3 check.keyof
-" Checks whether {argument} is a key of {var}
-let s:args.check.keyof=['var']
+"▶3 check.key
+" Checks whether {argument} is a key of {var}. Matcher is ignored
+let s:args.check.key=['var', '?omtchr']
+"▶3 check.in
+" Checks whether {argument} is inside list {var}. Matcher is ignored
+let s:args.check.in=['var', '?omtchr']
 "▶3 check.hkey
 " Checks whether {argument} is a dictionary with given keys
 let s:args.check.hkey=['*string']
 "▶3 check.isfunc
 " Checks whether {argument} is a callable function reference. Additional 
-" argument determines whether strings should be accepted
+" argument determines whether strings should not be accepted
 let s:args.check.isfunc=['?one']
 "▶3 check.isreg
 " Checks whether {argument} is a valid regular expression
 let s:args.check._=[]
 let s:args.check.any=s:args.check._
 "▶2 arg
-let s:args.arg=copy(s:args.check)
+let s:args.argcheck=copy(s:args.check)
+let s:args.argfilter=extend(copy(s:args.check), s:args.pipe)
 "▶2 matcher
 let s:args.matcher={}
 "▶3 matcher.func
 " Output: context(intfunc, {funcname}[, contexts])
 function s:parser.intfunc()
     let type=self.l[0]
+    if type is 'arg'
+        let type.=self.type
+    endif
     let c=self.readc()
     let func=s:F.getmatch(s:args[type], c)
     if func is 0 "▶2
             \'[': [type([]),  ']'],
             \'"': [type(""),  '"'],
             \"'": [type(''),  "'"],
-            \'.': [type(0.0), '0'],
             \'-': [type(0),   '0'],
         \}
 let s:typewords={
             \    'string': type(''),
             \    'number': type(0),
-            \     'float': type(0.0),
             \'dictionary': type({}),
             \      'list': type([]),
             \  'function': 2,
         \}
+if has('float')
+    let s:typechars['.']=[type(0.0), '0']
+    let s:typewords.float=type(0.0)
+endif
 function s:parser.gettype()
     if !self.len
         call self.throw('typemis')
 endfunction
 "▶1 getstring  :: &self!
 " Gets either a string or variable name
-" Input: {str}
-"      | "$" {var}
-"      | {wordchar}+
-"      | .
+" Input: ( "$" {var} | {str} | {wordchar}+ )?
 " Output: add({string}) | *getvar
 function s:parser.getstring()
-    let c=self.readc()
-    if c is '"'
-        call self.add(self.readstr())
-    elseif c is "'"
-        call self.add(self.readsstr())
-    elseif c is '$'
-        call self.getvar()
-    elseif c=~#'^\w'
-        call self.add(c)
+    if self.len
+        let c=self.readc()
+        if c is '$'
+            call self.getvar()
+        elseif c is '"'
+            call self.add(self.readstr())
+        elseif c is "'"
+            call self.add(self.readsstr())
+        elseif c=~#'^\w'
+            call self.add(c)
+        else
+            call self.add('').ungetc(c)
+        endif
     else
-        call self.add('').ungetc(c)
+        call self.add('')
     endif
     return self
 endfunction
 function s:parser.getmatcher()
     return self.addcon('matcher').intfunc().conclose()
 endfunction
+"▶1 getomtchr  :: &self
+" Input: ( "~" {intfunc} )?
+" Output: context(matcher, {intfunc})?
+function s:parser.getomtchr()
+    let c=self.readc()
+    if c is '~'
+        return self.getmatcher()
+    endif
+    return self.ungetc(c)
+endfunction
 "▶1 scanfie    :: contextName + self → self + self
 " Input: {intfunc}
 "      | "*" {func}
     return self.conclose()
 endfunction
 "▶1 scanopts   :: &self
-" Input: "-" "("? ( "no"? ( "only" ) )* ")"
+" Input: "-" "("? ( "no"? ( "only" | "recursive" ) )* ")"
 " Output: add(<self.o>)
-let s:options={'only': 0,}
+let s:options={'only': 0, 'recursive': 0}
 function s:parser.scanopts()
     let c=self.readc()
     let self.o=copy(s:options)
 plugin/frawor/fwc/compiler:invlen
 ::: Section <Pipes/Function pipes>
 ::: Section <Pipes/Expression pipes>
+::: Section <Pipes/Built-in pipes>
+plugin/frawor/fwc/compiler:nsfunc
+::: Section <Pipes/Built-in pipes/Substitute>
+plugin/frawor/fwc/compiler:typefail
+::: Section <Pipes/Built-in pipes/Bool>
 ::: Section <Pipes/Pipes composition>
 ::: Section <Checks/Function checks>
 plugin/frawor/fwc/compiler:funcfail
 plugin/frawor/fwc/compiler:funcfail
 ::: Section <Checks/Expression checks>
 plugin/frawor/fwc/compiler:exprfail
+::: Section <Checks/Built-in checks>
+plugin/frawor/fwc/compiler:funcfail
+plugin/frawor/fwc/compiler:exprfail
+plugin/frawor/fwc/compiler:nsfunc
+plugin/frawor/fwc/compiler:typefail
+plugin/frawor/fwc/compiler:nbool
+plugin/frawor/fwc/compiler:nbool
+plugin/frawor/fwc/compiler:typefail
+plugin/frawor/fwc/compiler:nreg
+plugin/frawor/fwc/compiler:nmatch
+plugin/frawor/fwc/compiler:typefail
+plugin/frawor/fwc/compiler:nmatch
+::: Section <Checks/Built-in checks/Types>
+plugin/frawor/fwc/compiler:typefail
+plugin/frawor/fwc/compiler:typefail
+plugin/frawor/fwc/compiler:typefail
+plugin/frawor/fwc/compiler:typefail
+plugin/frawor/fwc/compiler:typefail
+plugin/frawor/fwc/compiler:typefail
+plugin/frawor/fwc/compiler:typefail
+plugin/frawor/fwc/compiler:typefail
+plugin/frawor/fwc/compiler:typefail
+plugin/frawor/fwc/compiler:typefail
+plugin/frawor/fwc/compiler:typefail
+plugin/frawor/fwc/compiler:typesfail
 ::: Section <Checks/Check composition>
 plugin/frawor/fwc/compiler:exprfail
 plugin/frawor/fwc/compiler:exprfail
 #▶2 Expression pipes
 ['|=string(@.@)',              'filter'], ['abc'], ["'abc'"]
 #▶2 Built-in pipes
+['func$"string"',          'filter'], ['abc'],              ["'abc'"]
+['func revstring',         'filter'], ['abc'],              ['cba']
+['eval string(@.@)',       'filter'], ['abc'],              ["'abc'"]
+['run [abc]',              'filter'], ['string'],           ["'abc'"]
+['run [abc]',              'filter'], [function("string")], ["'abc'"]
+['earg',                   'filter'], ['string("abc")'],    ["'abc'"]
+['isfunc',                 'filter'], [0],                  0
+['isfunc',                 'filter'], [function("tr")],     [function("tr")]
+#▶3 Substitute
+['substitute/abc/"def"""', 'filter'], ['abc'],              ['def']
+['substitute/abc/"def"""', 'filter'], [0],                  0
+['substitute/abc/"def"g',  'filter'], ['abcabc'],           ['defdef']
+['substitute/abc/"def"""', 'filter'], ['abcabc'],           ['defabc']
+['substitute$regex$replacement$flags', 'filter'], ['abc'],  ['defdefdef']
+#▶3 Bool
+['bool',                   'filter'], [[]],                 [0]
+['bool',                   'filter'], [{}],                 [0]
+['bool',                   'filter'], [""],                 [0]
+['bool',                   'filter'], [0],                  [0]
+['bool',                   'filter'], [1],                  [1]
+['bool',                   'filter'], ["abc"],              [1]
+['bool',                   'filter'], [[0]],                [1]
+['bool',                   'filter'], [{1:2}],              [1]
 #▶2 Pipes composition
 ['|*$"string"|*$"string"',     'filter'], ['abc'], ["'''abc'''"]
 ['|=string(@.@)|=string(@.@)', 'filter'], ['abc'], ["'''abc'''"]
 ['?=empty(@.@)', 'check'], ['abc'], 0
 ['?=empty(@.@)', 'check'], [''],    1
 #▶2 Built-in checks
+['func ordered',    'check'], ['abc'],              1
+['func ordered',    'check'], ['acb'],              0
+['eval empty(@.@)', 'check'], ['abc'],              0
+['eval empty(@.@)', 'check'], [''],                 1
+['isfunc',          'check'], [function("tr")],     1
+['isfunc',          'check'], [function("s:Eval")], 0
+['isfunc',          'check'], ["tr"],               1
+['isfunc 1',        'check'], ["tr"],               0
+['bool',            'check'], [""],                 0
+['bool',            'check'], [0],                  1
+['bool',            'check'], [1],                  1
+['bool',            'check'], [2],                  0
+['isreg',           'check'], [0],                  0
+['isreg',           'check'], ['\(abc\)'],          1
+['isreg',           'check'], ['\(abc'],            0
+['_',               'check'], [[[[]]]],             1
+['any',             'check'], [[[[]]]],             1
+['match/\vb@<!a/',  'check'], ['abc'],              1
+['match/\vb@<!a/',  'check'], ['bac'],              0
+['match/\vb@<!a/',  'check'], [0],                  0
+['match$regex',     'check'], [''],                 0
+['match$regex',     'check'], ['abc'],              1
+#▶3 Types
+['type string',     'check'], [''],                 1
+['type string',     'check'], [0],                  0
+['type ""',         'check'], [''],                 1
+['type ""',         'check'], [0],                  0
+["type ''",         'check'], [''],                 1
+["type ''",         'check'], [0],                  0
+['type number',     'check'], [0],                  1
+['type number',     'check'], [[]],                 0
+['type -0',         'check'], [0],                  1
+['type -0',         'check'], [[]],                 0
+['type -',          'check'], [0],                  1
+['type -',          'check'], [[]],                 0
+['type dictionary', 'check'], [{}],                 1
+['type dictionary', 'check'], [[]],                 0
+['type {}',         'check'], [{}],                 1
+['type {}',         'check'], [[]],                 0
+['type list',       'check'], [[]],                 1
+['type list',       'check'], [""],                 0
+['type []',         'check'], [[]],                 1
+['type []',         'check'], [""],                 0
+['type function',   'check'], [function("tr")],     1
+['type function',   'check'], [function("s:Eval")], 1
+['type function',   'check'], ["tr"],               0
+# Not checking float here: it may be absent on some setups
+['type {}, []',     'check'], [{}],                 1
+['type {}, []',     'check'], [[]],                 1
+['type {}, []',     'check'], [""],                 0
 #▶2 Check composition
 ['?=!empty(@.@)?=(type(@.@)==type(""))?=(@.@[0]=="a")', 'check'], ['abc'], 1
 ['?=!empty(@.@)?=(type(@.@)==type(""))?=(@.@[0]=="a")', 'check'], [''],    0

test/rtp/plugin/fwccheck.vim

 function s:.ordered(str)
     return join(sort(split(a:str, '\v.@=')), '') is a:str
 endfunction
+let s:regex='\v.'
+let s:replacement='def'
+let s:flags='g'
 for line in readfile('fwctests.dat')
     if empty(line)
         continue