Commits

ZyX_I  committed 0a1245f

@/fwc: Some fixes:
- Fixed `start' matcher not accepting match in case when one alternative is
another alternative+suffix
- Fixed prefixes inside actions interaction
- Improved -onlystrings option handling (untested)
- Some typo fixes

  • Participants
  • Parent commits 8012474

Comments (0)

Files changed (7)

File doc/frawor.txt

             function.
         There are some predefined decorators:
         Decorator   Description ~
+                                                         *frawor-de-altervars*
         @altervars  Uses |:try| .. |:finally| to alter variables/options and 
                     restore them afterwards. Accepts a list as an argument, 
                     see |frawor-t-altervars|. Priority: 192.
                     Defined in plugin/frawor/decorators/altervars.
+                                                           *frawor-de-checker*
         @checker    Checks function arguments before running the function 
                     itself. See |frawor-t-checker| for description of an 
                     argument. Priority: 128. Defined in plugin/frawor/checks.
+                                                            *frawor-de-filter*
         @filter     Filters function arguments before running the function 
                     itself. See |frawor-t-filter| for description of an 
                     argument. Priority: 64. Defined in plugin/frawor/checks.
-        @FWC        Accepts tuple with two elements: FWC string and type. 
+                                                               *frawor-de-FWC*
+        @FWC        Accepts tuple with two elements: |FWC| string and type. 
                     Depending on type, acts like @checker (if type is "check") 
                     or @filter (if type is "filter"). Unlike @checker and 
                     @filter does not create additional function.
         failed when this function returns 0.
         If checker is a string, then it is passed to |frawor-f-fwc_compile| 
         with "check" as a second argument, then generated function is 
-        returned.
+        returned. Note that script variables won't be accessible here. Use FWC 
+        decorator (|frawor-de-FWC|) if you want access to your script 
+        variables.
 filter :: Function                                           *frawor-t-filter*
         If filter is a function reference, then in order to filter arguments 
         this function will be called with an argument list as an only 
         argument list. Function should return 0 if some error occured.
         If filter is a string, then it is passed to |frawor-f-fwc_compile| 
         with "filter" as a second argument, then generated function is 
-        returned.
+        returned. Note that script variables won't be accessible here. Use FWC 
+        decorator (|frawor-de-FWC|) if you want access to your script 
+        variables.
 option :: {}                                                 *frawor-t-option*
         Dictionary which describes option. It has the following keys:
         Key         Description ~

File plugin/frawor/fwc/compiler.vim

 call extend(s:_messages, map({
             \  'funcfail': 'custom function returned 0',
             \  'exprfail': 'custom expression returned 0',
+            \ 'nostrings': 'strings are not allowed here',
             \ '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',
     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
+        if type(value)==type('')
+            if value is a:str
+                let r=[value]
+                break
+            elseif ((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
         endif
         unlet value
                     \                       'self.getvar(v:val)'), ', ').']']
     elseif a:var[0] is 'evaluate'
         let r=[eval(substitute(self.getvar(a:var[1]), '@%@', 'self.vars', 'g'))]
+        if type(r[0])!=type('')
+            let r[0]=s:F.string(r[0])
+        endif
     elseif a:var[0] is 'func'
         let r=[self.getfunc(a:var, 0)]
     elseif a:var[0] is 'this'
     "▶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
     "▶2 `expr' pipe
     elseif a:pipe[1][0] is 'expr'
         call self.let(curargstr, self.getexpr(a:pipe[1], curargstr))
+        let self.typechanged=1
     "▶2 Built-in pipes
     elseif a:pipe[1][0] is 'intfunc'
         let desc=a:pipe[1][1:]
 endfunction
 "▶1 addtypecond    :: types, idx + self → self + self
 function s:compiler.addtypecond(types, idx)
-    if self.o.onlystrings
+    if self.o.onlystrings && self.argbase is '@@@'
+        if index(types, type(''))==-1
+            call self.nextthrow(1, 'nostrings', a:idx)
+        endif
         return self
     endif
     let curargstr=self.argstr()
     let pmsgnum=len(self.msgs.own)
     let msg=[]
     let i=0
+    let savedonlystrings=self.o.onlystrings
     for proc in arg
         let compargs=[proc, a:idx.'.'.i]
         let comptype=proc[0]
             let compargs[0]=[comptype, compargs[0]]
         endif
         call call(self['compile'.comptype], compargs, self)
+        if self.typechanged
+            let self.o.onlystrings=0
+            let self.typechanged=0
+        endif
         let i+=1
     endfor
+    let self.o.onlystrings=savedonlystrings
     if len(self.msgs.own)>pmsgnum
         call remove(self.msgs.own, pmsgnum, -1)
     endif
         call self.let(caidxstr, oldsub)
     endif
     let self.subs[-1]=copy(caidx)
-    "▶2 `optinal' key
+    "▶2 `optional' key
     if has_key(a:adescr, 'optional')
         unlet nextsub
         let nextsub=copy(caidx)
         let haslist=!empty(lists)
         let lastliststr=self.getlvarid('lastlist')
         let addtry=(has_key(a:adescr, 'next') || has_key(a:adescr, 'actions'))
+        let largsstr=self.getlargsstr()
         "▶3 Add messages saving if required
         if addtry
             if !addedsavemsgs
                     \.call('insert('.base.', '.prefdictstr.', '.
                     \                s:F.getsub(nextsub).')')
                     \.increment(caidxstr)
+                    \.increment(largsstr)
         "▶3 Add default values
         for [prefix, prefopts, defval; dummylist] in defaults
             call self.let(prefdictstr.self.getsubs([prefix]),
         else
             call self.nextthrow(astr.' is 0', 'pnf', idx, self.argstr())
         endif
+        call self.increment(largsstr, -1)
         if addtry
             call self.let(argorigstr, removestr)
         else
                             \.incsub()
             endfor
             "▶3 Move prefix arguments to prefix dictionary
+            if prefopts.argnum>0
+                call self.increment(largsstr, -prefopts.argnum)
+            endif
             if prefopts.list
                 let removestr='remove('.base.', '.caidxstr.', '.
                             \           caidxstr.'+'.(prefopts.argnum-1).')'
                             \.let(prefstr, 0)
                             \.up()
             endif
-            "▶3 Discard arguments length cache
-            let key=string([self.argbase]+self.subs[:-2])[1:-2]
-            if has_key(self.lavars, key)
-                unlet self.lavars[key]
-            endif
             "▲3
         endfor
         call self.up()
                 \.catch(s:cfreg)
                     \.if('exists('.string(argorigstr).')')
                         \.call('insert('.base.', '.argorigstr.', '.caidxstr.')')
+                        \.increment(largsstr)
                     \.up()
                 \.up()
         else
                 \           },
                 \'failcal': [],
                 \ 'lavars': {},
+                \'typechanged': 0,
             \})
     let tree=s:_r.fwc_parser(a:string, a:type)[1:]
     call extend(t, s:compiler, 'error')
     execute "function d.f(args)\n    ".
                 \substitute(substitute(substitute(
                 \join(lines, "\n    "),
-                \'@@@', ((a:type is 'complete')?
-                \           ('variants'):
-                \           ((opts.only)?('d.arg'):
-                \                        ('a:args'))), 'g'),
+                \'@@@', ((opts.only)?('d.arg'):
+                \                    ('a:args')), 'g'),
                 \'@%@', 's:vars.'.id,    'g'),
                 \'@$@', '',              'g')."\n".
             \"endfunction"

File plugin/frawor/fwc/constructor.vim

             elseif type is 'let'
                 call add(r, istr.'let '.remove(item, 0).'='.remove(item, 0))
             elseif type is 'inc'
-                call add(r, istr.'let '.remove(item, 0).'+='.remove(item, 0))
+                let lhs=remove(item, 0)
+                let assign='+='
+                let shift=remove(item, 0)
+                if type(shift)==type(0) && shift<0
+                    let assign='-='
+                    let shift=-shift
+                endif
+                call add(r, istr.'let '.lhs.assign.shift)
             elseif       type is 'call'   ||
                         \type is 'throw'  ||
                         \type is 'return' ||

File plugin/frawor/fwc/intfuncs.vim

 let s:r.run={'args': ['var']}
 function s:r.run.pipe(desc, idx, type)
     let curargstr=self.argstr()
-    return self.compilecheck(['check', ['intfunc', 'isfunc', 0]], a:idx)
+    call self.compilecheck(['check', ['intfunc', 'isfunc', 0]], a:idx)
             \.try()
                 \.let(curargstr, 'call('.curargstr.', '.
                 \                                self.getvar(a:desc[1]).', {})')
             \.up().catch()
                 \.addthrow('runfail', 1, a:idx, 'v:exception')
+    let self.typechanged=1
+    return self
 endfunction
 "▶1 `earg'
 " Replaces {argument} with the result of evaluating itself
 let s:r.earg={'args': []}
 function s:r.earg.pipe(desc, idx, type)
     let curargstr=self.argstr()
-    return self.addtypecond([type('')], a:idx)
+    call self.addtypecond([type('')], a:idx)
             \.try()
                 \.let(curargstr, 'eval('.curargstr.')')
             \.up().catch()
                 \.addthrow('evalfail', 1, a:idx, 'v:exception')
+    let self.typechanged=1
+    return self
 endfunction
 "▶1 `not'
 let s:r.not={'args': ['arg']}
     let curargstr=self.argstr()
     let varstr=self.getvar(a:desc[1])
     let matchstr=self.getlvarid('match')
-    return self.addtypecond([type('')], a:idx)
+    call self.addtypecond([type('')], a:idx)
                 \.let(matchstr, self.getmatcher(a:desc[2], varstr,
                 \                               curargstr))
                 \.nextthrow(matchstr.' is 0', 'nmatch', a:idx, curargstr)
                 \.let(curargstr, varstr.'['.matchstr.']')
+    let self.typechanged=1
+    return self
 endfunction
 "▶1 `substitute'
 " Runs substitute on {argument}
 let s:r.range={'args': ['number', 'number']}
 function s:r.range.check(desc, idx, type)
     let curargstr=self.argstr()
-    "▶4 Determine whether we accept floating-point values
+    "▶2 Determine whether we accept floating-point values
     let acceptfloat=has('float') &&
                 \(a:desc[1][0] is 'float' || a:desc[1][1] is 'float')
     if acceptfloat
     else
         call self.addtypecond([type(0)], a:idx)
     endif
-    "▶4 Obtain range borders
+    "▶2 Obtain range borders
     let range=map(a:desc[1:],
                 \'((v:val[0] is "inf" || v:val[0] is "nan")?'.
                 \   '(""):'.
     if type(range[0])!=type('') && type(range[1])!=type('') && range[0]>range[1]
         call reverse(range)
     endif
-    "▶4 Construct condition
+    "▶2 Construct condition
     let cond=''
     call map(range, '((type(v:val)=='.type('').')?'.
                 \               '(v:val):'.
         endif
         let cond.=curargstr.'>'.range[1]
     endif
-    "▶4 Add condition to result
+    "▶2 Add condition to result
     if !empty(cond)
         call self.nextthrow(cond, 'nrange', a:idx,
                     \                       'string('.curargstr.')',
                     \                       'string('.range[0].')',
                     \                       'string('.range[1].')')
     endif
+    "▲2
     return self
 endfunction
 "▶1 `match'
 " Transforms {argument} to 0 if it is empty and to 1 otherwise
 function s:r.bool.pipe(desc, idx, type)
     let curargstr=self.argstr()
-    return self.let(curargstr, '!empty('.curargstr.')')
+    let self.typechanged=1
+    if self.o.onlystrings
+        return self.let(curargstr, curargstr.'=~#''\c\v^%(1|yes|ok|true)$''')
+    else
+        return self.let(curargstr, '!empty('.curargstr.')')
+    endif
 endfunction
 "▶1 `is'
 " Checks whether {argument} is {var}

File plugin/frawor/fwc/parser.vim

 endfunction
 "▶1 delblanks  :: &self(s)
 function s:parser.delblanks()
-    return self.removestr(match(self.s, '\S'))
+    return self.removestr(match(self.s, "[^ \t\n\r]"))
 endfunction
 "▶1 ungetc     :: (Char) + self → self + self(s)
 function s:parser.ungetc(c)
     return self
 endfunction
 "▶1 getsubscr  :: &self!
-" Input: ( "."? ( ":" {n} {n} | {subscript} ) )*
+" Input: ( "." ( ":" {n} {n} | {subscript} ) )*
 "        {subscript} :: {str}
 "                     | [0-9] {wordchar}*
 "                     | {wordchar}+
             call self.add(+c)
         elseif c=~#'^\w'
             call self.add(c)
+        elseif c is '-'
+            call self.add(-self.getc())
         elseif c is '"'
             call self.add(self.readstr())
         elseif c is "'"
 "      | "=" {expr}
 "      | "[" {list}
 "      | "$" {var}
+"      | "*" {func}
 "      | "(" {var} ")"
 "      | {str}
 " Output: context(plugvar, String, {subscr}*)
 "▶1 scanpref   :: &self
 " Input: {omtchr} ({prefdescr} ( "-" | {arg} )* )* "}"?
 "        {prefdescr} :: {prefopts}? ( {str} | {wordchar}+ )
-"         {prefopts} :: ( "?" | "!" | "*" | "+" {digit}+ )* ( ":" {var} )?
+"         {prefopts} :: ( "?" | "!" | "*" | "+" {wordchar}+ )* ( ":" {var} )?
 " Output: context(prefixes[, {matcher}], {prefix}*)
 "           {prefix} :: context(prefix, String, {prefopts}[, {var}][, {arg}*])
 "         {prefopts} :: { "alt": Bool, "list": Bool, "opt": Bool,

File test/fwccheck.ok

 plugin/frawor/fwc/compiler:pnf
 plugin/frawor/fwc/compiler:nreg
 plugin/frawor/fwc/compiler:pnf
+plugin/frawor/fwc/compiler:pnf
 ::: Section <Required, optional, actions, prefixes and next composition>
 plugin/frawor/fwc/compiler:tooshort
 <<< messages

File test/fwctests.dat

 ['isfunc',          'check'], ['()'],               0
 ['isfunc',          'check'], ['g:[xx]'],           0
 #▶3 match
+:set magic
 ['match/\vb@<!a/',  'check'], ['abc'],              1
 ['match/\vb@<!a/',  'check'], ['bac'],              0
 ['match/\vb@<!a/',  'check'], [0],                  0
 ['{!+2 abc bool bool}',           'filter'], ['noa'],     [{'abc': 0}]
 ['{abc isreg}',                   'filter'], ['a','\v('], 0
 ['{!abc !abd}',                   'filter'], ['a', 'a'],  0
+['{abc :=(2) -}',                 'filter'], ['a'],       [{'abc': 1}]
+['{abc :=(2) -}',                 'filter'], [],          [{'abc': 2}]
+['{abc :=(2) -}',                 'filter'], ['noa'],     0
+['{?text - ?texthl -}',           'filter'], ['text'],    [{'text': 1}]
 ['{*b isreg !?a}', 'filter'], ['b', 'n', 'n'], [{'b': ['n'], 'a': 0}]
 ['{*b isreg !?a}', 'filter'], ['b', 'n', 'c'], [{'b': ['n', 'c']}]
 #▶1 Required, optional, actions, prefixes and next composition
 ['{?ab isreg} <ab bool>',     'filter'], ['a', '\v('],      [{}, 'ab', 1]
 ['{?ab isreg} + type string', 'filter'], ['a', '\v('],      [{}, 'a', '\v(']
 ['{?ab isreg} + type string', 'filter'], ['a', '\v'],       [{'ab': '\v'}]
+['<a {abc isreg} b [isreg]>', 'filter'], ['a', 'abc', ''],  ['a', {'abc': ''}]
+['<a {abc isreg} b [isreg]>', 'filter'], ['b'],             ['b']
+['<a {abc isreg} b [isreg]>', 'filter'], ['b', ''],         ['b', '']
 ['type "" [type -0] {ab isreg} + type [] <cd bool>', 'filter'], ['', 0, 'a', '-', [1], 'c', {}], ['', 0, {'ab': '-'}, [1], 'cd', 0]
 # vim: cms=#%s fmr=▶,▲