ZyX_I avatar ZyX_I committed f57fa10

Some checks scanner improvements (I won't describe them until it is finished)

Comments (0)

Files changed (1)

plugin/frawor/checks.vim

             \  'msgmis': 'missing message description',
             \ 'prefmis': 'missing prefixes description',
             \  'actmis': 'missing arguments description',
+            \ 'typemis': 'missing type description',
+            \ 'invtype': 'invalid type description: %s',
+            \  'invreg': 'invalid regular expression: %s',
+            \ 'wordend': 'regular expression cannot end with %s',
         \})
 "▶1 conschecker feature
 let s:checkers={'lastid': 0}
 "▶1 pipe
 let s:F.pipe={}
 let s:args.pipe={}
+"▶2 pipe.substitute
+" Runs substitute on {argument}
+let s:args.pipe.substitute=['reg', 'string', 'string']
+"▶2 pipe.list
+" Picks up first element from {var}::List that matches {argument}
+let s:args.pipe.list=['var', 'matcher']
+"▶2 pipe.key
+" Picks up first key from {var}::Dictionary that matches {argument}
+let s:args.pipe.key=['var']
+"▶2 pipe.take
+" Replaces {argument} with value of the first key from {var}::Dictionary that 
+" matches {argument}
+let s:args.pipe.take=['var', 'matcher']
+"▶2 pipe.func
+" Replaces {argument} with the result of running {func}({argument})
+let s:args.pipe.func=['func']
+"▶2 pipe.eval
+" Replaces {argument} with the result of evaluating {expr}
+let s:args.pipe.eval=['expr']
+"▶2 pipe.run
+" Replaces {argument} with the result of calling itself with {list} as argument 
+" list
+let s:args.pipe.run=['list']
+"▶2 pipe.earg
+" Replaces {argument} with the result of evaluating itself
+let s:args.pipe.earg=[]
 "▶1 func
 let s:F.func={}
 let s:args.func={}
 "▶1 arg
 let s:F.arg={}
 let s:args.arg={}
+"▶2 arg.func
+" Checks whether result of running {func}({argument}) isnot 0
+let s:args.arg.func=['func']
+"▶2 arg.eval
+" Checks whether result of running eval({expr}) isnot 0
+let s:args.arg.eval=['expr']
+"▶2 arg.type
+" Checks whether {argument} has one of given types
+let s:args.arg.type=['*type']
+"▶2 arg.bool
+" Checks whether {argument} is either 0 or 1
+let s:args.arg.bool=[]
+"▶2 arg.keyof
+" Checks whether {argument} is a key of {var}
+let s:args.arg.keyof=['var']
+"▶2 arg.hkey
+" Checks whether {argument} is a dictionary with given keys
+let s:args.arg.hkey=['*string']
+"▶2 arg.isfunc
+" Checks whether {argument} is a callable function reference. Additional 
+" argument determines whether strings should be accepted
+let s:args.arg.isfunc=['?one']
+"▶2 arg.isreg
+" Checks whether {argument} is a valid regular expression
+let s:args.arg.isreg=[]
+"▶2 arg.match
+" Checks whether {argument} is a string that matches {reg}
+let s:args.arg.match=['reg']
+"▶2 arg.dict
+" Checks whether {argument} is a dictionary matching given {ddescr}
+let s:args.arg.dict=['ddescr']
+"▶2 arg.tuple
+" Checks whether {argument} is a list with a fixed length and each element 
+" matching given specification
+let s:args.arg.tuple=['*arg']
+"▶2 arg.path
+" Checks whether {argument} is a path matching given specification
+let s:args.arg.path=['path']
+"▶2 arg._, arg.any
+" Unconditionally accepts {argument}
+let s:args.arg._=[]
+let s:args.arg.any=s:args.arg._
 "▶1 matcher
 let s:F.matcher={}
 let s:args.matcher={}
+"▶2 matcher.exact
+" Requires exact match. Additional argument determines whether match should be 
+" done case-insensitively
+let s:args.matcher.exact=['?one']
+"▶2 matcher.start
+" Requires match at the start of string. First optional arguments determines 
+" case sensitivity, second determines whether ambigious matches should be 
+" accepted (in this case first match in sorted list will be taken)
+let s:args.matcher.start=['?one', '?one']
+"▶2 matcher.smart
+" `smart' matcher tries to gues what element is really ment. Additional argument 
+" determines whether ambigious match should be accepted (in this case first 
+" match in sorted list will be taken)
+let s:args.matcher.smart=['?one']
 "▶1 getmatch        :: Dict, String → String
 function s:F.getmatch(dict, str)
-    let lstr=len(a:str)
+    if has_key(a:dict, a:str)
+        return a:str
+    endif
+    let lstr=len(a:str)-1
     for key in sort(keys(a:dict))
-        if key[:(ltsr-1)] is a:str
+        if key[:(lstr)] is a:str
             return key
         endif
     endfor
 endfunction
 "▶1 scan
 let s:F.scan={}
+"▶2 scan.add        :: &self
+" Adds an element to current context
+function s:F.scan.add(item)
+    call add(self.l, a:item)
+    return self
+endfunction
 "▶2 scan.conclose   :: &self
+" Closes current context
 function s:F.scan.conclose()
     call remove(self.stack, -1)
     let self.l=self.stack[-1]
     return self
 endfunction
-"▶2 scan.addcon     :: ()[, coninit] + self → self + self
+"▶2 scan.addcon     :: ()[, conelement1[, ...]] + self → self + self
+" Adds new context with given elements
 function s:F.scan.addcon(...)
     let con=copy(a:000)
-    call add(self.l, con)
+    call self.add(con)
     call add(self.stack, con)
     let self.l=con
     return self
     return self
 endfunction
 "▶2 scan.delblanks  :: &self(s)
+" Removes blanks (tabs, spaces and newlines)
 function s:F.scan.delblanks()
-    let self.s=substitute(self.s, '^\s\+', '', 'g')
+    let self.s=substitute(self.s, '^\_s\+', '', 'g')
     let self.eos=empty(self.s)
     return self
 endfunction
 "▶2 scan.nextc      :: () + self → String + self(s)
+" Gets next character or word
 function s:F.scan.nextc()
     if self.eos
         call self.throw('eos')
         let self.s=self.s[len(c):]
     endif
     call self.delblanks()
-    let self.eos=empty(self.s)
     return c
 endfunction
 "▶2 scan.nextstr    :: () + self → String + self(s)
+" Gets next double-quoted string. Backslash just escapes next character, no 
+" other translations are done
 function s:F.scan.nextstr()
     if !empty(self.ungot)
         call self.throw('int', 'strungetc')
     endif
-    let c=matchstr(self.s, '\v(\\.|[^\\"]*)+"')
+    let c=matchstr(self.s, '\v(\\.|[^\\"])+"')
     if empty(c)
         call self.throw('unmatchp', '"')
     endif
     let self.s=self.s[len(c):]
-    let self.eos=empty(self.s)
+    call self.delblanks()
     return substitute(c[:-2], '\\\(.\)', '\1', 'g')
 endfunction
+"▶2 scan.nextreg    :: endstr + self → String + self(s)
+" Gets the next regular expression. {endstr} determines border character
+function s:F.scan.nextreg(endstr)
+    if !empty(self.ungot)
+        call self.throw('int', 'regungetc')
+    endif
+    if a:endstr=~#'^\w'
+        call self.throw('wordend', a:endstr)
+    endif
+    let c=matchstr(self.s, '\v(\\.|[^\\'.escape(a:endstr, '\]^-').'])+'.
+                \          '\V'.escape(a:endstr, '\'))
+    if empty(c)
+        call self.throw('unmatchp', a:endstr)
+    endif
+    let self.s=self.s[len(c):]
+    try
+        call matchstr("", c)
+    catch
+        call self.throw('invreg', c)
+    endtry
+    call self.delblanks()
+    return c[:-2]
+endfunction
 "▶2 scan.scanlist   :: farg + self → self
+" Scans a list of elements that looks either like "{e1}, {e2}", "({e1} {e2})" or 
+" "({e1}, {e2})" (last two can be combined).
 function s:F.scan.scanlist(farg)
-    let c=self.nextc()
-    if c is '('
-        while !self.eos
-            let c=self.nextc()
-            if c is ')'
-                break
-            elseif c isnot ','
-                call self['get'.a:farg]()
-            endif
-        endwhile
-    else
-        call self.ungetc(c)
-        while !self.eos
-            call self['get'.a:farg]()
-            if !self.eos
+    call self.addcon()
+    if !self.eos
+        let c=self.nextc()
+        if c is '('
+            while !self.eos
                 let c=self.nextc()
-                if c isnot ','
+                if c is ')'
+                    break
+                elseif c isnot ','
                     call self.ungetc(c)
-                    break
+                    call call(self['get'.a:farg], [], self)
                 endif
-            endif
-        endwhile
+            endwhile
+        else
+            call self.ungetc(c)
+            while !self.eos
+                call call(self['get'.a:farg], [], self)
+                if !self.eos
+                    let c=self.nextc()
+                    if c isnot ','
+                        call self.ungetc(c)
+                        break
+                    endif
+                endif
+            endwhile
+        endif
     endif
-    return self
+    return self.conclose()
 endfunction
 "▶2 scan.intfunc    :: &self
+" Gets pipe, func, argument or matcher that may accept one or more arguments. 
+" Arguments are described in s:args and have a form [?][*]{aname}, where {aname} 
+" is a part of get* function name, ? determines whether argument can be omitted 
+" (it really makes a difference only at the end of the string), * says that 
+" string should be scanned for a list of arguments with arbitrary length, not 
+" just for a single one
 function s:F.scan.intfunc()
     let type=self.stack[-1][0]
     if self.eos
         call self.throw('funcmis', type)
     endif
     let c=self.nextc()
-    let func=s:F.getmatch(s:F[type], c)
+    let func=s:F.getmatch(s:args[type], c)
     if func is 0
         call self.throw("ukfunc", c)
     endif
     call self.addcon('intfunc', func)
     let fargs=get(s:args[type], func, [])
     for farg in fargs
-        if self.eos
+        if farg[0] is '?'
+            let farg=farg[1:]
+        elseif self.eos
             call self.throw('argmis', type.'.'.func)
-        elseif farg[0] is '*'
+        endif
+        if farg[0] is '*'
             call self.scanlist(farg[1:])
         else
-            call self['get'.farg]()
+            call call(self['get'.farg], [], self)
         endif
     endfor
     return self.conclose()
 endfunction
+"▶2 scan.getone     :: &self!
+" Consumes a word and adds 1 to context if next word is "1", otherwise adds 0. 
+" If next word is "0", it is consumed.
+function s:F.scan.getone()
+    let c=self.nextc()
+    if c is '1'
+        call self.add(1)
+    else
+        call self.add(0)
+        if c isnot '0'
+            call self.ungetc(c)
+        endif
+    endif
+    return self
+endfunction
+"▶2 scan.gettype    :: &self!
+" Adds type number to the context.
+" Valid arguments: s[tring], n[umber], f[loat], d[ictionary], l[ist], fu[nction]
+"                   "", '',     -0,      .0,         {},        []
+let s:typechars={
+            \'{': [type({}),  '}'],
+            \'[': [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,
+        \}
+function s:F.scan.gettype()
+    if self.eos
+        call self.throw('typemis')
+    endif
+    let c=self.nextc()
+    if has_key(s:typechars, c)
+        call self.add(s:typechars[c][0])
+        if !self.eos
+            let nextchar=s:typechars[c][1]
+            let c=self.nextc()
+            if c isnot nextchar
+                call self.ungetc(c)
+            endif
+        endif
+    else
+        let type=s:F.getmatch(s:typewords, tolower(c))
+        if type is 0
+            call self.throw('invtype', c)
+        endif
+        call self.add(s:typewords[type])
+    endif
+    return self
+endfunction
+"▶2 scan.getstring  :: &self!
+" Gets either a string or variable name
+function s:F.scan.getstring()
+    let c=self.nextc()
+    if c is '"'
+        call self.add(self.nextstr())
+    elseif c is '$'
+        call self.getvar()
+    elseif c=~#'^\w'
+        call self.add(c)
+    else
+        call self.add("").ungetc(c)
+    endif
+    return self
+endfunction
+"▶2 scan.getpath    :: &self!
+" Gets path specification:
+" 1. "d" for directory and "f" for regular file, otherwise both may be accepted
+" 2. "r" for readable file (not directory)
+" 3. "w" for writeable file or directory (unless "f" is specified),
+"    "W" for writeable file or directory (unless "f" is specified) or
+"        non-existant file in writeable directory (unless "d" is specified),
+"    "p" like "W", but also accepts any path that can be created (for example,
+"        if you have directory /a and you can write to it, then path /a/b/c/d 
+"        will be accepted),
+" 4. "x" for executable file (not directory)
+function s:F.scan.getpath()
+    let c=self.nextc()
+    if c=~#'^[df]\=r\=[wWp]\=x\=$' && c!~#'^d\%(.\{,2}x\|r\)'
+        call self.add(c)
+    else
+        call self.add('r').ungetc(c)
+    endif
+    return self
+endfunction
+"▶2 scan.getarg     :: &self!
+" Simple wrapper to scan.scan()
+function s:F.scan.getarg()
+    return self.scan()
+endfunction
+"▶2 scan.getddescr  :: &self
+function s:F.scan.getddescr()
+    call self.addcon('ddescr')
+    call self.nextc() " Skip `{'
+    while !self.eos
+        let c=self.nextc()
+        if c is '}'
+            break
+        elseif c is '"'
+            call self.addcon('eq', self.nextstr()).scan().conclose()
+        elseif c is '/'
+            call self.addcon('regex', self.nextreg('/')).scan().conclose()
+        else
+            call self.addcon('check').ungetc(c).scan().scan().conclose()
+        endif
+    endwhile
+    return self.conclose()
+endfunction
+"▶2 scan.getreg     :: &self
+let s:pairs={
+            \'(': ')',
+            \'[': ']',
+            \'{': '}',
+            \'<': '>',
+        \}
+function s:F.scan.getreg()
+    call self.addcon('regex')
+    let c=self.nextc()
+    if c is '$'
+        call self.getvar()
+    else
+        call self.add(self.nextreg(get(s:pairs, c, c)))
+    endif
+    return self.conclose()
+endfunction
+"▶2 scan.getexpr    :: &self
+let s:parens={'(': ')', '[': ']', '{': '}'}
+let s:revparens={}
+call map(copy(s:parens), 'extend(s:revparens, {v:val : v:key})')
+function s:F.scan.getexpr()
+    let r=""
+    call self.addcon('expr')
+    let parens=[]
+    while !self.eos
+        let c=self.nextc()
+        if has_key(s:parens, c)
+            call add(parens, s:parens[c])
+        elseif c is '"'
+            let c.=substitute(escape(self.nextstr(),'\"'), "\n", '\\n', 'g').'"'
+        elseif has_key(s:revparens, c)
+            if empty(parens)
+                call self.ungetc(c)
+                break
+            elseif c is parens[-1]
+                call remove(parens, -1)
+            else
+                call self.throw('unmatchp', s:revparens[c])
+            endif
+        endif
+        let r.=c
+    endwhile
+    let r.=join(parens, "")
+    return self.add(r).conclose()
+endfunction
 "▶2 scan.getchvar   :: &self
 function s:F.scan.getchvar()
     call self.addcon('this', 0)
     if !self.eos
         let c=self.nextc()
         if c is '<'
-            call self.addcon('this', 1)
+            let self.l[-1]+=1
             while !self.eos
                 let c=self.nextc()
                 if c is '<'
                             endif
                             let v.=self.nextc()
                         endif
-                        call add(self.l, +v)
+                        call self.add(+v)
                     endfor
+                    call self.conclose()
                 elseif c isnot '.'
                     break
                 endif
         if c is ']'
             break
         elseif c is '"'
-            call add(self.l, self.nexstr())
+            call self.add(self.nexstr())
         else
-            call add(self.l, c)
+            call self.add(c)
         endif
     endwhile
     return self.conclose()
                 break
             endif
         endwhile
+        call self.conclose()
     elseif c is '@'
         return self.getchvar()
+    elseif c is '='
+        return self.getexpr()
     elseif c is '"'
-        call self.addcon('svar', self.nextstr())
+        call self.addcon('string', self.nextstr()).conclose()
     elseif c is '['
         call self.getlist()
     else
         call self.throw('invvar', c)
     endif
-    return self.conclose()
+    return self
 endfunction
 "▶2 scan.getfunc    :: &self
 function s:F.scan.getfunc()
                         call self.getvar()
                     elseif c is '*'
                         call self.getfunc()
-                    elseif c is '@'
-                        call self.getchvar()
+                    elseif c=~#'^\w'
+                        call self.addcon('string', c).conclose()
                     else
                         call self.throw('ukfarg', c)
                     endif
     endif
     return self.conclose()
 endfunction
-"▶2 scan.getpipe    :: &self
-function s:F.scan.getpipe()
+"▶2 scan.getmatcher :: &self
+function s:F.scan.getmatcher()
+    return self.addcon('matcher').intfunc().conclose()
+endfunction
+"▶2 scan.scanpipe   :: &self
+function s:F.scan.scanpipe()
     if self.eos
         call self.throw('pipemis')
     endif
     endif
     return self.conclose()
 endfunction
-"▶2 scan.getmsg     :: &self
-function s:F.scan.getmsg()
-    call self.addcon('msg')
+"▶2 scan.scanmsg    :: &self
+function s:F.scan.scanmsg()
     if self.eos
         call self.throw('msgmis')
     endif
-    let c=self.nextc()
-    " TODO
+    call self.addcon('msg', self.nextc())
+    if !self.eos
+        let c=self.nextc()
+        if self.c is '('
+            while !self.eos
+                let c=self.nextc()
+                if c is ')'
+                    break
+                elseif c is '.'
+                    call self.addcon('curval').conclose()
+                elseif c is '%'
+                    call self.addcon('curarg').conclose()
+                else
+                    call self.getvar()
+                endif
+            endwhile
+        else
+            call self.ungetc(c)
+        endif
+    endif
     return self.conclose()
 endfunction
 "▶2 scan.scanopt    :: &self
     endif
     let c=self.nextc()
     if c is '~'
-        call self.addcon("matcher").intfunc().conclose()
+        call self.getmatcher()
     else
         call self.ungetc(c)
     endif
     endif
     let c=self.nextc()
     if c is '~'
-        call self.addcon("matcher").intfunc().conclose()
+        call self.getmatcher()
     else
         call self.ungetc(c)
     endif
                     endif
                     call self.ungetc(c).scan()
                 endwhile
+                call self.conclose()
             else
                 call self.throw('noadescr')
             endif
     while !self.eos
         let c=self.nextc()
         if c is '|'
-            call self.getpipe()
+            call self.scanpipe()
         elseif c is '#'
-            call self.getmsg()
+            call self.scanmsg()
         elseif (!hastext || hasparen || self.o.only) && c=~#'^\w'
             let hastext=1
             call self.ungetc(c).intfunc()
     endif
     return self.conclose()
 endfunction
-"▶2 scan.getoptions :: &self
+"▶2 scan.scanopts   :: &self
 let s:options={'only': 0,}
-function s:F.scan.getoptions()
+function s:F.scan.scanopts()
     let c=self.nextc()
     if c is '-'
         call self.nextc()  " Here must be a parenthesis
             elseif has_key(self.o, c)     " -(option)
                 let self.o[c]=1
             elseif has_key(self.o, c[2:]) " -(nooption)
-                let self.o[c]=0
+                let self.o[c[2:]]=0
             else
                 call self.throw('uoption', c)
             endif
     else
         call self.ungetc(c)
     endif
+    return self
 endfunction
 "▶2 scan.string     :: String → SynTree
 function s:F.scan.string(string)
     let s.warn=s:_f.warn
     call add(s.stack, s.tree)
     let s.l=s.stack[-1]
-    call s.delblanks().getoptions()
+    call s.delblanks().scanopts().add(s.o)
     while !s.eos
         call s.scan()
     endwhile
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.