Commits

ZyX_I committed 807fb8c

@/checks, @/fwc: Moved scanner to a different module, started working on compiler

Comments (0)

Files changed (10)

plugin/frawor/checks.vim

             \exists('g:frawor__donotload')
     finish
 endif
-execute frawor#Setup('0.0', {}, 1)
-let s:args={}
+execute frawor#Setup('0.0', {'@/fwc': '0.0'}, 1)
 "▶1 _messages
 if v:lang=~?'ru'
     let s:_messages={
                 \            'plugin %s: ID is not a string',
             \}
 endif
-call extend(s:_messages, {
-            \     'int': 'internal error: %s',
-            \     'eos': 'unexpected end of string',
-            \  'ukfunc': 'unknown function: %s',
-            \  'ukfarg': 'unknown function argument: %s',
-            \'unmatchp': 'unmatched `%s''',
-            \ 'uenvvar': 'unknown enviroment variable: %s',
-            \  'invvar': 'invalid variable description: %s',
-            \ 'invpref': 'invalid prefix description: %s',
-            \  'invact': 'invalid action description: %s',
-            \'invssubs': 'invalid subscript in slice expression: %s',
-            \ 'uoption': 'unknown option: %s',
-            \  'argmis': 'missing arguments to %s',
-            \ 'subsmis': 'subscript range missing',
-            \   'nonum': 'number expected',
-            \  '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',
-            \  'noexpr': 'expected expression, but got nothing',
-        \})
 "▶1 conschecker feature
 let s:checkers={'lastid': 0}
 "▶2 conschecker  :: {f}, checker → chkfunc + s:checkers
 call s:_f.newfeature('consfilter', {'cons': s:F.consfilter,
             \                     'unload': s:F.delfilters,
             \                       'init': {'ids': []}})
-"▶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 check
-let s:F.check={}
-let s:args.check={}
-"▶2 check.func
-" Checks whether result of running {func}({argument}) isnot 0
-let s:args.check.func=['func']
-"▶2 check.eval
-" Checks whether result of running eval({expr}) isnot 0
-let s:args.check.eval=['expr']
-"▶2 check.type
-" Checks whether {argument} has one of given types
-let s:args.check.type=['*type']
-"▶2 check.bool
-" Checks whether {argument} is either 0 or 1
-let s:args.check.bool=[]
-"▶2 check.range
-" Checks whether {argument} is in given range
-let s:args.check.range=['number', 'number']
-"▶2 check.keyof
-" Checks whether {argument} is a key of {var}
-let s:args.check.keyof=['var']
-"▶2 check.hkey
-" Checks whether {argument} is a dictionary with given keys
-let s:args.check.hkey=['*string']
-"▶2 check.isfunc
-" Checks whether {argument} is a callable function reference. Additional 
-" argument determines whether strings should be accepted
-let s:args.check.isfunc=['?one']
-"▶2 check.isreg
-" Checks whether {argument} is a valid regular expression
-let s:args.check.isreg=[]
-"▶2 check.match
-" Checks whether {argument} is a string that matches {reg}
-let s:args.check.match=['reg']
-"▶2 check.dict
-" Checks whether {argument} is a dictionary matching given {ddescr}
-let s:args.check.dict=['ddescr']
-"▶2 check.tuple
-" Checks whether {argument} is a list with a fixed length and each element 
-" matching given specification
-let s:args.check.tuple=['*check']
-"▶2 check.either
-" Checks whether {argument} matches one of given specifications
-let s:args.check.either=['*check']
-"▶2 check.path
-" Checks whether {argument} is a path matching given specification
-let s:args.check.path=['path']
-"▶2 check._, check.any
-" Unconditionally accepts {argument}
-let s:args.check._=[]
-let s:args.check.any=s:args.check._
-"▶1 arg
-let s:args.arg=copy(s:args.check)
-"▶1 matcher
-let s:F.matcher={}
-let s:args.matcher={}
-"▶2 matcher.func
-" Uses some other function as matcher
-let s:args.matcher.func=['func']
-"▶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)
-    if has_key(a:dict, a:str)
-        return a:str
-    endif
-    let lstr=len(a:str)-1
-    return get(filter(sort(keys(a:dict)), 'v:val[:'.lstr.'] is a:str'), 0, 0)
-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     :: ()[, conelement1[, ...]] + self → self + self
-" Adds new context with given elements
-function s:F.scan.addcon(...)
-    let con=copy(a:000)
-    call self.add(con)
-    call add(self.stack, con)
-    let self.l=con
-    return self
-endfunction
-"▶2 scan.removestr  :: (UInt) + self → self + self(s)
-function s:F.scan.removestr(len)
-    if a:len>0
-        let self.s=self.s[(a:len):]
-        let self.len-=a:len
-    endif
-    return self
-endfunction
-"▶2 scan.delblanks  :: &self(s)
-function s:F.scan.delblanks()
-    return self.removestr(match(self.s, '\S'))
-endfunction
-"▶2 scan.ungetc     :: (Char) + self → self + self(s)
-function s:F.scan.ungetc(c)
-    call add(self.ungot, a:c)
-    let self.len+=len(a:c)
-    return self
-endfunction
-"▶2 scan.readc      :: () + self → String + self(s)
-" Gets next character or word
-function s:F.scan.readc()
-    if !empty(self.ungot)
-        let c=remove(self.ungot, -1)
-        let self.len-=len(c)
-        return c
-    endif
-    call self.delblanks()
-    if !self.len
-        call self.throw('eos')
-    endif
-    let c=self.s[0]
-    if c!~#'\w'
-        call self.removestr(1)
-    else
-        let c=matchstr(self.s, '^\w\+')
-        call self.removestr(len(c))
-    endif
-    return c
-endfunction
-"▶2 scan.readstr    :: () + self → String + self(s)
-" Gets next double-quoted string. Backslash just escapes next character, no 
-" other translations are done
-" {str} :: '"' ( ( ! '\' | '"' ) | ( '\\' | '\"' ) )* '"'
-function s:F.scan.readstr()
-    if !empty(self.ungot)
-        call self.throw('int', 'strungetc')
-    endif
-    let c=matchstr(self.s, '\v(\\.|[^\\"])*"')
-    if empty(c)
-        call self.throw('unmatchp', '"')
-    endif
-    call self.removestr(len(c))
-    return substitute(c[:-2], '\\\(.\)', '\1', 'g')
-endfunction
-"▶2 scan.readreg    :: endstr + self → String + self(s)
-" Gets the next regular expression. {endstr} determines border character
-" {reg} :: ( "\" . | ! "\" {endstr} ) {endstr}
-" {endstr} :: {char} \ {wordchar}
-function s:F.scan.readreg(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
-    call self.removestr(len(c))
-    try
-        call matchstr('', c)
-    catch
-        call self.throw('invreg', c)
-    endtry
-    return c[:-2]
-endfunction
-"▶2 scan.readflt    :: () + self → String|0 + self(s)
-"  {flt} :: ( "+" | "-" ) ( "nan" | "inf" | {unum} )
-" {unum} :: {d}* "."? {d}* ( "e" ( "+" | "-" )? [0-9]+ )?
-"    {d} :: [0-9] | "_"
-function s:F.scan.readflt()
-    if !empty(self.ungot)
-        call self.throw('int', 'fltungetc')
-    endif
-    call self.delblanks()
-    let c=matchstr(self.s,
-                \'\v\c^[+-]? *%(nan|inf|[0-9_]*\.?[0-9_]*%(e[+-]?\d+)?)')
-    call self.removestr(len(c))
-    if empty(c)
-        return 0
-    endif
-    return substitute(substitute(substitute(substitute(tolower(c),
-                \'[_ ]',           '',     'g'),
-                \'^[+-]\=\d\@!',   '&0',   '' ),
-                \'\.\d\@!',        '.0',   '' ),
-                \'\v^[+-]?\d+e@=', '&.0',  '' )
-endfunction
-"▶2 scan.readexpr   :: () + self → String + self(s)
-let s:parens={'(': ')', '[': ']', '{': '}'}
-let s:revparens={}
-call map(copy(s:parens), 'extend(s:revparens, {v:val : v:key})')
-function s:F.scan.readexpr()
-    if !empty(self.ungot)
-        call self.throw('int', 'fltungetc')
-    endif
-    call self.delblanks()
-    let c=''
-    let parens=[]
-    while !empty(self.s)
-        let chunk=matchstr(self.s, '\v^.{-}[''[\](){}"]')
-        let stopsym=''
-        if empty(chunk)
-            let chunk=self.s
-        else
-            let stopsym=chunk[-1:]
-        endif
-        if has_key(s:parens, stopsym)
-            call add(parens, s:parens[stopsym])
-        elseif has_key(s:revparens, stopsym)
-            let close=''
-            while !empty(parens) && parens[-1] isnot stopsym
-                let close.=remove(parens, -1)
-            endwhile
-            let c.=close
-            if empty(parens)
-                break
-            else
-                call remove(parens, -1)
-            endif
-        elseif stopsym is '"'
-            let string=matchstr(self.s, '\v(\\.|[^\\"])*"', len(chunk))
-            if empty(string)
-                call self.throw('unmatchp', '"')
-            else
-                let chunk.=string
-            endif
-        elseif stopsym is "'"
-            let string=matchstr(self.s, '\v(''''|[^''])*''', len(chunk))
-            if empty(string)
-                call self.throw('unmatchp', "'")
-            else
-                let chunk.=string
-            endif
-        endif
-        call self.removestr(len(chunk))
-        let c.=chunk
-    endwhile
-    let c.=join(parens, '')
-    if empty(c)
-        call self.throw('noexpr')
-    endif
-    return c
-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).
-" Input: {<farg>} ( "," {<farg>} )*
-"      | "(" ( {<farg>} ","? )* ")"?
-" Output: context({<farg>}*)
-function s:F.scan.scanlist(farg)
-    call self.addcon()
-    if self.len
-        let c=self.readc()
-        if c is '('
-            while self.len
-                let c=self.readc()
-                if c is ')'
-                    break
-                elseif c isnot ','
-                    call self.ungetc(c)
-                    call call(self['get'.a:farg], [], self)
-                endif
-            endwhile
-        else
-            call self.ungetc(c)
-            while self.len
-                call call(self['get'.a:farg], [], self)
-                if self.len
-                    let c=self.readc()
-                    if c isnot ','
-                        call self.ungetc(c)
-                        break
-                    endif
-                endif
-            endwhile
-        endif
-    endif
-    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
-" Input: {funcname} [arguments]?
-"        {funcname} :: {wordchar}+
-" Output: context(intfunc, {funcname}[, contexts])
-function s:F.scan.intfunc()
-    let type=self.stack[-1][0]
-    let c=self.readc()
-    let func=s:F.getmatch(s:args[type], c)
-    if func is 0 "▶3
-        call self.throw('ukfunc', c)
-    endif        "▲3
-    call self.addcon('intfunc', func)
-    let fargs=get(s:args[type], func, [])
-    for farg in fargs
-        if farg[0] is '?'
-            let farg=farg[1:]
-        elseif !self.len "▶3
-            call self.throw('argmis', type.'.'.func)
-        endif            "▲3
-        if farg[0] is '*'
-            call self.scanlist(farg[1:])
-        else
-            call call(self['get'.farg], [], self)
-        endif
-    endfor
-    return self.conclose()
-endfunction
-"▶2 scan.getarg     :: &self!
-" Simple wrapper to scan.scan()
-function s:F.scan.getarg()
-    return self.scan()
-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.
-" Input: ( "0" | "1" )?
-" Output: add(0|1)
-function s:F.scan.getone()
-    if !self.len
-        call self.add(0)
-    else
-        let c=self.readc()
-        if c is '1'
-            call self.add(1)
-        else
-            call self.add(0)
-            if c isnot '0'
-                call self.ungetc(c)
-            endif
-        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,         {},        []
-" Input: {typedescr}
-" Output: add({typeNumber})
-"         {typeNumber}: any number described in |type()|
-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.len
-        call self.throw('typemis')
-    endif
-    let c=self.readc()
-    if has_key(s:typechars, c)
-        call self.add(s:typechars[c][0])
-        if self.len
-            let readchar=s:typechars[c][1]
-            let c=self.readc()
-            if c isnot readchar
-                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
-" Input: {str}
-"      | "$" {var}
-"      | {wordchar}+
-"      | .
-" Output: add({string}) | *getvar
-function s:F.scan.getstring()
-    let c=self.readc()
-    if c is '"'
-        call self.add(self.readstr())
-    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:
-" Input: [( "d" | "f" )] [ "r" ] [( "w" | "W" | "p" )] [ "x" ]
-"        & ! ( "d" ) "r"
-"        & ! ( "d" ) [( "w" | "W" | "p" )] "x"
-" 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)
-" Output: add({pathspec})
-function s:F.scan.getpath()
-    let c=self.readc()
-    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.getsubscr  :: &self!
-" Input: ( "." ( "."? ( ":" {n} {n} | {subscript} ) )* )
-"        {subscript} :: {str}
-"                     | [0-9] {wordchar}*
-"                     | {wordchar}+
-"                {n} :: "-"? {wordchar}+
-" Output: add((String|Number|context(Number, Number))*)
-function s:F.scan.getsubscr()
-    while self.len
-        let c=self.readc()
-        if c is '"'
-            call self.add(self.readstr())
-        elseif c=~#'^\d'
-            call self.add(+c)
-        elseif c=~#'^\w'
-            call self.add(c)
-        elseif c is ':'
-            call self.addcon()
-            " Start and end subscripts
-            for i in range(0, 1)
-                if !self.len
-                    call self.throw('subsmis')
-                endif
-                let v=self.readc()
-                if v is '-'
-                    if !self.len
-                        call self.throw('nonum')
-                    endif
-                    let v.=self.readc()
-                endif
-                call self.add(+v)
-            endfor
-            call self.conclose()
-        elseif c isnot '.'
-            call self.ungetc(c)
-            break
-        endif
-    endwhile
-    return self
-endfunction
-"▶2 scan.getddescr  :: &self
-" Gets dictionary description:
-" Input: "{" ({keydescr} {arg})* "}"
-"        {keydescr} :: {str}
-"                    | "/" {reg}(endstr=/)
-"                    | {arg}
-" Output: context(ddescr, [{keycon}])
-"         {keycon} :: context(eq,    String, {arg})
-"                   | context(regex, String, {arg})
-"                   | context(check, {arg},  {arg})
-function s:F.scan.getddescr()
-    call self.addcon('ddescr')
-    if self.len
-        let c=self.readc()
-        if c isnot '{'
-            call self.ungetc(c)
-        endif
-        let prevlen=-1
-        while self.len && self.len!=prevlen
-            let prevlen=self.len
-            let c=self.readc()
-            if c is '}'
-                break
-            elseif c is '"'
-                call self.addcon('eq', self.readstr()).scan().conclose()
-            elseif c is '/'
-                call self.addcon('regex', self.readreg('/')).scan().conclose()
-            else
-                call self.addcon('check').ungetc(c).scan().scan().conclose()
-            endif
-        endwhile
-    endif
-    return self.conclose()
-endfunction
-"▶2 scan.getreg     :: &self
-" Gets regular expression
-" Input: "$" {var}
-"      | {startchar} {reg}(endchar={endchar})
-"           {startchar} :: ! ( {wordchar} | "$" )
-"             {endchar} :: pair({startchar}) | {startchar}
-" Output: context(regex, {var}|String)
-let s:pairs={
-            \'(': ')',
-            \'[': ']',
-            \'{': '}',
-            \'<': '>',
-        \}
-function s:F.scan.getreg()
-    call self.addcon('regex')
-    let c=self.readc()
-    if c is '$'
-        call self.getvar()
-    else
-        call self.add(self.readreg(get(s:pairs, c, c)))
-    endif
-    return self.conclose()
-endfunction
-"▶2 scan.getnumber  :: &self
-" Get integer or floating point number (including ±inf and nan)
-" Input: {flt}
-"      | "$" {var}
-"      | ""
-" Output: context(inf, "+" | "-")
-"       | context(nan)
-"       | context(number, Number)
-"       | context(float, Float)
-"       | *getvar
-function s:F.scan.getnumber()
-    let f=self.readflt()
-    if f is 0
-        let c=self.readc()
-        if c is '$'
-            return self.getvar()
-        else
-            call self.ungetc(c)
-        endif
-    elseif f[-3:] is 'inf'
-        let sign=f[0]
-        if f[0] is 'i'
-            let sign='+'
-        endif
-        return self.addcon('inf', sign).conclose()
-    elseif f is 'nan'
-        return self.addcon('nan').conclose()
-    endif
-    let r=eval(f)
-    if type(r) is type(0)
-        return self.addcon('number', r).conclose()
-    else
-        return self.addcon('float', r).conclose()
-    endif
-endfunction
-"▶2 scan.getexpr    :: &self
-" Input: {expr}
-" Output: context(expr, String)
-function s:F.scan.getexpr()
-    return self.addcon('expr', self.readexpr()).conclose()
-endfunction
-"▶2 scan.getchvar   :: &self
-" Input: "<"* {subscr}?
-" Output: context(this, Number, {subscr}?)
-function s:F.scan.getchvar()
-    call self.addcon('this', 0)
-    if self.len
-        let c=self.readc()
-        if c is '<'
-            let self.l[-1]+=1
-            while self.len
-                let c=self.readc()
-                if c is '<'
-                    let self.l[-1]+=1
-                else
-                    break
-                endif
-            endwhile
-        endif
-        if c is '.'
-            call self.getsubscr()
-        else
-            call self.ungetc(c)
-        endif
-    endif
-    return self.conclose()
-endfunction
-"▶2 scan.getlist    :: &self
-" Input: ( "$" {var} | "*" {func} | "=" {expr} | {str} | {wordchar}+ )* "]"?
-" Output: context(list, ({var}|{expr}|String)*)
-function s:F.scan.getlist()
-    call self.addcon('list')
-    while self.len
-        let c=self.readc()
-        if c is ']'
-            break
-        elseif c is '$'
-            call self.getvar()
-        elseif c is '='
-            call self.getexpr()
-        elseif c is '*'
-            call self.getfunc()
-        elseif c is '"'
-            call self.add(self.nextstr())
-        elseif c=~#'^\w'
-            call self.add(c)
-        endif
-    endwhile
-    return self.conclose()
-endfunction
-"▶2 scan.getvar     :: &self
-" Input: {wordchar}+ {subscr}*
-"      | "@" {chvar}
-"      | "=" {expr}
-"      | "[" {list}
-"      | {str}
-" Output: context(plugvar, String, {subscr}*)
-"       | {chvar}
-"       | {expr}
-"       | {list}
-function s:F.scan.getvar()
-    let c=self.readc()
-    if c=~#'^\w'
-        call self.addcon('plugvar', c).getsubscr().conclose()
-    elseif c is '@'
-        return self.getchvar()
-    elseif c is '='
-        return self.getexpr()
-    elseif c is '"'
-        call self.addcon('string', self.readstr()).conclose()
-    elseif c is '['
-        call self.getlist()
-    else
-        call self.throw('invvar', c)
-    endif
-    return self
-endfunction
-"▶2 scan.getfunc    :: &self
-" Input: {var} ( "(" ( "$" {var} | "*" {func} | {wordchar}+ ) ")"? )?
-" Output: context(func, {var}, ({var}|{func}|context(string, String))*)
-function s:F.scan.getfunc()
-    call self.addcon('func').getvar()
-    if self.len
-        let c=self.readc()
-        if c is '('
-            while self.len
-                let c=self.readc()
-                if c is ')'
-                    break
-                elseif c is '$'
-                    call self.getvar()
-                elseif c is '*'
-                    call self.getfunc()
-                elseif c=~#'^\w'
-                    call self.addcon('string', c).conclose()
-                else
-                    call self.throw('ukfarg', c)
-                endif
-            endwhile
-        else
-            call self.ungetc(c)
-        endif
-    endif
-    return self.conclose()
-endfunction
-"▶2 scan.getmatcher :: &self
-" Input: {intfunc}
-" Output: context(matcher, {intfunc})
-function s:F.scan.getmatcher()
-    return self.addcon('matcher').intfunc().conclose()
-endfunction
-"▶2 scan.scanfie      :: contextName + self → self + self
-" Input: {intfunc}
-"      | "*" {func}
-"      | "=" {expr}
-" Output: context(<contextName>, {intfunc}|{func}|{expr})
-function s:F.scan.scanfie(cname)
-    call self.addcon(a:cname)
-    let c=self.readc()
-    if c is '*'
-        call self.getfunc()
-    elseif c is '='
-        call self.getexpr()
-    else
-        call self.ungetc(c).intfunc()
-    endif
-    return self.conclose()
-endfunction
-"▶2 scan.scanmsg    :: &self
-" Input: {wordchar}+ ( "(" ( "." | "%" | {var} ) ")"? )?
-" Output: context(msg, String, {msgarg}*)
-"         {msgarg} :: context(curval)
-"                   | context(curarg)
-"                   | {var}
-function s:F.scan.scanmsg()
-    call self.addcon('msg', self.readc())
-    if self.len
-        let c=self.readc()
-        if c is '('
-            while self.len
-                let c=self.readc()
-                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
-" Input: {arg}* "]"?
-" Output: context(optional, ({arg}|context(default, {var}, {arg}))*)
-function s:F.scan.scanopt()
-    call self.addcon('optional')
-    let prevlen=-1
-    while self.len && self.len!=prevlen
-        let prevlen=self.len
-        let c=self.readc()
-        if c is ']'
-            break
-        elseif c is '='
-            call self.addcon('default').getvar().scan().conclose()
-        else
-            call self.ungetc(c).scan()
-        endif
-    endwhile
-    return self.conclose()
-endfunction
-"▶2 scan.scanpref   :: &self
-" Input: ({prefdescr} {arg})* "}"?
-"        {prefdescr} :: ( "?" | "*" )* ( "=" {var} )? ({str} | {wordchar}+)
-" Output: context(prefixes, context(String, {prefopts}[, {var}], {arg}))
-"         {prefopts} :: { "alt": Bool, "list": Bool }
-let s:defprefopts={'alt': 0, 'list': 0}
-function s:F.scan.scanpref()
-    call self.addcon('prefixes')
-    let c=self.readc()
-    if c is '~'
-        call self.getmatcher()
-    else
-        call self.ungetc(c)
-    endif
-    let prevlen=-1
-    while self.len && prevlen!=self.len
-        let prevlen=self.len
-        let c=self.readc()
-        if !exists('pref')
-            if !exists('prefopts')
-                let prefopts=copy(s:defprefopts)
-            endif
-            if c is '"'
-                let c=self.readstr()
-                let pref=c
-            elseif c=~#'^\w'
-                let pref=c
-            elseif c is '?'
-                let prefopts.alt=1
-            elseif c is '*'
-                let prefopts.list=1
-            elseif c is '}'
-                break
-            else
-                call self.throw('invpref', c)
-            endif
-        else
-            call self.addcon('prefix', pref, prefopts)
-            if c is '='
-                call self.getvar()
-            else
-                call self.ungetc(c)
-            endif
-            call self.scan().conclose()
-            unlet pref prefopts
-        endif
-    endwhile
-    return self.conclose()
-endfunction
-"▶2 scan.scanact    :: &self
-" Input: "~" {matcher} ( {actdescr} ( "-" | "(" {arg}* ")"? | {arg} ) )* ">"?
-"        {actdescr} :: {str}
-"                    | {wordchar}+
-" Output: context(actions[, {matcher}],
-"                 (context(action, 0|String|{arg}, {arg}+))
-"                 (context(0|String|context(actdescr, {arg}),
-"                          context(actargs, {arg}+)))*)
-function s:F.scan.scanact()
-    call self.addcon('actions')
-    let c=self.readc()
-    if c is '~'
-        call self.getmatcher()
-    else
-        call self.ungetc(c)
-    endif
-    let hasaction=0
-    let prevlen=-1
-    while self.len && prevlen!=self.len
-        let prevlen=self.len
-        let c=self.readc()
-        if !hasaction
-            let hasaction=1
-            if c is '>'
-                let hasaction=0
-                break
-            elseif c is '"'
-                call self.addcon('action', self.readstr())
-            elseif c is '-'
-                call self.addcon('action', 0)
-            elseif c=~#'^\w'
-                call self.addcon('action', c)
-            else
-                call self.ungetc(c).addcon('action').scan()
-            endif
-        else
-            let hasaction=0
-            if c is '('
-                while self.len
-                    let c=self.readc()
-                    if c is ')'
-                        break
-                    endif
-                    call self.ungetc(c).scan()
-                endwhile
-            elseif c isnot '>' && c isnot '-'
-                call self.ungetc(c).scan()
-            endif
-            call self.conclose()
-        endif
-    endwhile
-    if hasaction
-        call self.conclose()
-    endif
-    return self.conclose()
-endfunction
-"▶2 scan.scan       :: &self
-" Input: "[" {opt}  ⎫
-"      | "{" {pref} ⎬ (only at toplevel or in context(action or optional))
-"      | "<" {act}  ⎪
-"      | "+" {arg}  ⎭
-"      | "(" ( "|" {pipe} | "#" {msg} | {intfunc} )* ")"?
-"      | ( "|" {pipe} | "#" {msg} )* {intfunc}?
-" Output: {opt}
-"       | {pref}
-"       | {act}
-"       | context(next, {arg})
-"       | context(arg, ({pipe}|{check}|{msg}|{intfunc})*)
-function s:F.scan.scan()
-    let c=self.readc()
-    let type=self.stack[-1][0]
-    if type is 'top' || type is 'optional'
-                \|| (type is 'action' && len(self.l)>1)
-        if c is '['
-            return self.scanopt()
-        elseif c is '{'
-            return self.scanpref()
-        elseif c is '<'
-            return self.scanact()
-        elseif c is '+'
-            return self.addcon('next').scan().conclose()
-        endif
-    endif
-    call self.addcon('arg')
-    let accepttext=(type is 'next' || self.o.only)
-    let hasparen=0
-    let hastext=0
-    if c is '('
-        let hasparen=1
-        let accepttext=1
-    else
-        call self.ungetc(c)
-    endif
-    while self.len
-        let c=self.readc()
-        if c is '|'
-            call self.scanfie('pipe')
-        elseif c is '?'
-            call self.scanfie('check')
-        elseif c is '#'
-            call self.scanmsg()
-        elseif (!hastext || accepttext) && c=~#'^\w'
-            let hastext=1
-            call self.ungetc(c).intfunc()
-        elseif hasparen && c is ')'
-            break
-        else
-            call self.ungetc(c)
-            break
-        endif
-    endwhile
-    return self.conclose()
-endfunction
-"▶2 scan.scanopts   :: &self
-" Input: "-" "("? ( "no"? ( "only" ) )* ")"
-" Output: add(<self.o>)
-let s:options={'only': 0,}
-function s:F.scan.scanopts()
-    let c=self.readc()
-    let self.o=copy(s:options)
-    call self.add(self.o)
-    if c is '-'
-        let c=self.readc()
-        if c isnot '('
-            call self.ungetc(c)
-        endif
-        while self.len
-            let c=self.readc()
-            if c is ')'
-                break
-            elseif has_key(self.o, c)     " -(option)
-                let self.o[c]=1
-            elseif has_key(self.o, c[2:]) " -(nooption)
-                let self.o[c[2:]]=0
-            else
-                call self.throw('uoption', c)
-            endif
-        endwhile
-    else
-        call self.ungetc(c)
-    endif
-    return self
-endfunction
-"▶2 scan.string     :: String → SynTree
-function s:F.scan.string(string)
-    "▶3 Setup self
-    let s   =   {    's': a:string,
-                \ 'tree': ['top'],
-                \'stack': [],
-                \'ungot': [],
-                \    'l': 0,
-                \ 'type': 'check',
-                \  'len': len(a:string),
-                \}
-    call extend(s, s:F.scan, 'error')
-    " FIXME Redefine this function with more verbose one
-    let s.throw=s:_f.throw
-    call add(s.stack, s.tree)
-    let s.l=s.stack[-1]
-    "▲3
-    call s.scanopts()
-    if s.o.only
-        call s.scan()
-    else
-        let prevlen=-1
-        while s.len && s.len!=prevlen
-            let prevlen=s.len
-            call s.scan()
-        endwhile
-    endif
-    "▶3 Transform s.l[2:] to dictionary
-    let args=remove(s.l, 2, -1)
-    call add(s.l, {})
-    for arg in args
-        if !has_key(s.l[-1], arg[0])
-            let s.l[-1][arg[0]]=[]
-        endif
-        if arg[0] is 'actions' || arg[0] is 'prefixes'
-            call extend(s.l[-1][arg[0]], arg[1:])
-        else
-            call add(s.l[-1][arg[0]], arg[1:])
-        endif
-    endfor
-    "▲3
-    return s.tree
-endfunction
-"▶1 FIXME this is for debugging
-if !exists('g:curtest')
-    let g:scan=s:F.scan
-endif
 "▶1
 call frawor#Lockvar(s:, 'checkers,filters')
 " vim: fmr=▶,▲ sw=4 ts=4 sts=4 et tw=80

plugin/frawor/fwc.vim

+"▶1 Header
+scriptencoding utf-8
+if exists('s:_pluginloaded') || exists('g:fraworOptions._donotload') ||
+            \exists('g:frawor__donotload')
+    finish
+endif
+execute frawor#Setup('0.0', {'@/fwc/compiler': '0.0'}, 1)
+"▶1
+call frawor#Lockvar(s:, '')
+" vim: fmr=▶,▲ sw=4 ts=4 sts=4 et tw=80

plugin/frawor/fwc/compiler.vim

+"▶1 Header
+scriptencoding utf-8
+if exists('s:_pluginloaded') || exists('g:fraworOptions._donotload') ||
+            \exists('g:frawor__donotload')
+    finish
+endif
+execute frawor#Setup('0.0', {'@/fwc/parser': '0.0'}, 1)
+"▶1 s:F
+let s:F.compile={}
+let s:F.comp={} " For functions that do not require `self'
+"▶1 add, addcon, conclose
+" Adds an element to current context
+let s:F.add=s:_r.fwc_parser.add
+let s:F.addcon=s:_r.fwc_parser.addcon
+let s:F.conclose=s:_r.fwc_parser.conclose
+"▶1 cleanup      :: list::[_, {arg}*] → + list
+function s:F.cleanup(list)
+    if len(a:list)>1
+        let args=remove(a:list, 1, -1)
+        let sq={}
+        call add(a:list, sq)
+        for arg in args
+            if !has_key(sq, arg[0])
+                let sq[arg[0]]=[]
+            endif
+            if arg[0] is 'actions' || arg[0] is 'prefixes'
+                if get(get(arg, 1, []), 0, "") is 'matcher'
+                    let sq[arg[0].'matcher']=remove(arg, 1)[1:]
+                endif
+                call extend(sq[arg[0]], map(arg[1:], 'v:val[1:]'))
+            elseif arg[0] is 'next'
+                call add(sq[arg[0]], arg[1:][0][1:])
+            elseif arg[0] is 'optional'
+                call add(sq[arg[0]], arg)
+            else
+                call add(sq[arg[0]], arg[1:])
+            endif
+        endfor
+        call filter(sq, '!empty(v:val)')
+        for key in filter(['actions', 'optional'], 'has_key(sq, v:val)')
+            call map(copy(sq[key]), 's:F.cleanup(v:val)')
+            call filter(sq[key], 'len(v:val)>1')
+            if empty(sq[key])
+                call remove(sq, key)
+            endif
+        endfor
+        if has_key(sq, 'optional')
+            call map(sq.optional, 'v:val[1]')
+        endif
+    endif
+endfunction
+"▶1 getlenrange  :: adescr → (minlen, maxlen)
+function s:F.getlenrange(adescr)
+    let minimum=0
+    let maximum=0
+    if has_key(a:adescr, 'arg')
+        let minimum=len(a:adescr.arg)
+        let maximum=minimum
+    endif
+    if has_key(a:adescr, 'optional')
+        let lenlist=map(copy(a:adescr.optional), 's:F.getlenrange(v:val)')
+        let maximum+=max(map(copy(lenlist), 'v:val[1]'))
+    endif
+    if has_key(a:adescr, 'actions')
+        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
+                    let amin=namin
+                endif
+                if namax>amax
+                    let amax=namax
+                endif
+            endif
+        endfor
+        let minimum+=((has0)?(0):(hasa))+amin
+        let maximum+=hasa+amax
+    endif
+    if has_key(a:adescr, 'prefixes')
+    endif
+    return [minimum, maximum]
+endfunction
+"▶1 lencheck  :: &self
+"▶1 string
+function s:F.string(string)
+    "▶2 Setup self
+    let t   =   { 'tree': s:_r.fwc_parser.string(a:string)[1:],
+                \ 'type': 'check',
+                \'ctree': [],
+                \'stack': [],
+                \    'l': 0,
+                \ 'argn': 0,
+                \}
+    call extend(t, s:F, 'error')
+    let t.throw=s:_f.throw
+    call add(t.stack, t.ctree)
+    let t.l=t.stack[-1]
+    "▲2
+    call s:F.cleanup(t.tree)
+    " FIXME
+    echo s:F.getlenrange(t.tree[1])
+    return t.tree
+endfunction
+"▶1 FIXME this is for debugging
+if !exists('g:curtest')
+    let g:scan=s:_r.fwc_parser
+    let g:compile=s:F
+endif
+"▶1
+call frawor#Lockvar(s:, '')
+" vim: fmr=▶,▲ sw=4 ts=4 sts=4 et tw=80

plugin/frawor/fwc/parser.vim

+"▶1 Header
+scriptencoding utf-8
+if exists('s:_pluginloaded') || exists('g:fraworOptions._donotload') ||
+            \exists('g:frawor__donotload')
+    finish
+endif
+execute frawor#Setup('0.0', {'@/resources': '0.0'}, 1)
+"▶1 Define messages
+let s:_messages={
+            \     'int': 'internal error: %s',
+            \     'eos': 'unexpected end of string',
+            \  'ukfunc': 'unknown function: %s',
+            \  'ukfarg': 'unknown function argument: %s',
+            \'unmatchp': 'unmatched `%s''',
+            \ 'uenvvar': 'unknown enviroment variable: %s',
+            \  'invvar': 'invalid variable description: %s',
+            \  'invact': 'invalid action description: %s',
+            \'invssubs': 'invalid subscript in slice expression: %s',
+            \ 'uoption': 'unknown option: %s',
+            \  'argmis': 'missing arguments to %s',
+            \ 'subsmis': 'subscript range missing',
+            \   'nonum': 'number expected',
+            \  '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',
+            \  'noexpr': 'expected expression, but got nothing',
+        \}
+"▶1 s:args
+let s:args={}
+"▶2 pipe
+let s:args.pipe={}
+"▶3 pipe.substitute
+" Runs substitute on {argument}
+let s:args.pipe.substitute=['reg', 'string', 'string']
+"▶3 pipe.list
+" Picks up first element from {var}::List that matches {argument}
+let s:args.pipe.list=['var', 'matcher']
+"▶3 pipe.key
+" Picks up first key from {var}::Dictionary that matches {argument}
+let s:args.pipe.key=['var']
+"▶3 pipe.take
+" Replaces {argument} with value of the first key from {var}::Dictionary that 
+" matches {argument}
+let s:args.pipe.take=['var', 'matcher']
+"▶3 pipe.func
+" Replaces {argument} with the result of running {func}({argument})
+let s:args.pipe.func=['func']
+"▶3 pipe.eval
+" Replaces {argument} with the result of evaluating {expr}
+let s:args.pipe.eval=['expr']
+"▶3 pipe.run
+" Replaces {argument} with the result of calling itself with {list} as argument 
+" list
+let s:args.pipe.run=['list']
+"▶3 pipe.earg
+" Replaces {argument} with the result of evaluating itself
+let s:args.pipe.earg=[]
+"▶2 check
+let s:args.check={}
+"▶3 check.func
+" Checks whether result of running {func}({argument}) isnot 0
+let s:args.check.func=['func']
+"▶3 check.eval
+" Checks whether result of running eval({expr}) isnot 0
+let s:args.check.eval=['expr']
+"▶3 check.type
+" Checks whether {argument} has one of given types
+let s:args.check.type=['*type']
+"▶3 check.bool
+" Checks whether {argument} is either 0 or 1
+let s:args.check.bool=[]
+"▶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.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
+let s:args.check.isfunc=['?one']
+"▶3 check.isreg
+" Checks whether {argument} is a valid regular expression
+let s:args.check.isreg=[]
+"▶3 check.match
+" Checks whether {argument} is a string that matches {reg}
+let s:args.check.match=['reg']
+"▶3 check.dict
+" Checks whether {argument} is a dictionary matching given {ddescr}
+let s:args.check.dict=['ddescr']
+"▶3 check.tuple
+" Checks whether {argument} is a list with a fixed length and each element 
+" matching given specification
+let s:args.check.tuple=['*check']
+"▶3 check.either
+" Checks whether {argument} matches one of given specifications
+let s:args.check.either=['*check']
+"▶3 check.path
+" Checks whether {argument} is a path matching given specification
+let s:args.check.path=['path']
+"▶3 check._, check.any
+" Unconditionally accepts {argument}
+let s:args.check._=[]
+let s:args.check.any=s:args.check._
+"▶2 arg
+let s:args.arg=copy(s:args.check)
+"▶2 matcher
+let s:args.matcher={}
+"▶3 matcher.func
+" Uses some other function as matcher
+let s:args.matcher.func=['func']
+"▶3 matcher.exact
+" Requires exact match. Additional argument determines whether match should be 
+" done case-insensitively
+let s:args.matcher.exact=['?one']
+"▶3 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']
+"▶3 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)
+    if has_key(a:dict, a:str)
+        return a:str
+    endif
+    let lstr=len(a:str)-1
+    return get(filter(sort(keys(a:dict)), 'v:val[:'.lstr.'] is a:str'), 0, 0)
+endfunction
+"▶1 add        :: &self
+" Adds an element to current context
+function s:F.add(item)
+    call add(self.l, a:item)
+    return self
+endfunction
+"▶1 conclose   :: &self
+" Closes current context
+function s:F.conclose()
+    call remove(self.stack, -1)
+    let self.l=self.stack[-1]
+    return self
+endfunction
+"▶1 addcon     :: ()[, conelement1[, ...]] + self → self + self
+" Adds new context with given elements
+function s:F.addcon(...)
+    let con=copy(a:000)
+    call self.add(con)
+    call add(self.stack, con)
+    let self.l=con
+    return self
+endfunction
+"▶1 removestr  :: (UInt) + self → self + self(s)
+function s:F.removestr(len)
+    if a:len>0
+        let self.s=self.s[(a:len):]
+        let self.len-=a:len
+    endif
+    return self
+endfunction
+"▶1 delblanks  :: &self(s)
+function s:F.delblanks()
+    return self.removestr(match(self.s, '\S'))
+endfunction
+"▶1 ungetc     :: (Char) + self → self + self(s)
+function s:F.ungetc(c)
+    call add(self.ungot, a:c)
+    let self.len+=len(a:c)
+    return self
+endfunction
+"▶1 readc      :: () + self → String + self(s)
+" Gets next character or word
+function s:F.readc()
+    if !empty(self.ungot)
+        let c=remove(self.ungot, -1)
+        let self.len-=len(c)
+        return c
+    endif
+    call self.delblanks()
+    if !self.len
+        call self.throw('eos')
+    endif
+    let c=self.s[0]
+    if c!~#'\w'
+        call self.removestr(1)
+    else
+        let c=matchstr(self.s, '^\w\+')
+        call self.removestr(len(c))
+    endif
+    return c
+endfunction
+"▶1 readstr    :: () + self → String + self(s)
+" Gets next double-quoted string. Backslash just escapes next character, no 
+" other translations are done
+" {str} :: '"' ( ( ! '\' | '"' ) | ( '\\' | '\"' ) )* '"'
+function s:F.readstr()
+    if !empty(self.ungot)
+        call self.throw('int', 'strungetc')
+    endif
+    let c=matchstr(self.s, '\v(\\.|[^\\"])*"')
+    if empty(c)
+        call self.throw('unmatchp', '"')
+    endif
+    call self.removestr(len(c))
+    return substitute(c[:-2], '\\\(.\)', '\1', 'g')
+endfunction
+"▶1 readreg    :: endstr + self → String + self(s)
+" Gets the next regular expression. {endstr} determines border character
+" {reg} :: ( "\" . | ! "\" {endstr} ) {endstr}
+" {endstr} :: {char} \ {wordchar}
+function s:F.readreg(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
+    call self.removestr(len(c))
+    try
+        call matchstr('', c)
+    catch
+        call self.throw('invreg', c)
+    endtry
+    return c[:-2]
+endfunction
+"▶1 readflt    :: () + self → String|0 + self(s)
+"  {flt} :: ( "+" | "-" ) ( "nan" | "inf" | {unum} )
+" {unum} :: {d}* "."? {d}* ( "e" ( "+" | "-" )? [0-9]+ )?
+"    {d} :: [0-9] | "_"
+function s:F.readflt()
+    if !empty(self.ungot)
+        call self.throw('int', 'fltungetc')
+    endif
+    call self.delblanks()
+    let c=matchstr(self.s,
+                \'\v\c^[+-]? *%(nan|inf|[0-9_]*\.?[0-9_]*%(e[+-]?\d+)?)')
+    call self.removestr(len(c))
+    if empty(c)
+        return 0
+    endif
+    return substitute(substitute(substitute(substitute(tolower(c),
+                \'[_ ]',           '',     'g'),
+                \'^[+-]\=\d\@!',   '&0',   '' ),
+                \'\.\d\@!',        '.0',   '' ),
+                \'\v^[+-]?\d+e@=', '&.0',  '' )
+endfunction
+"▶1 readexpr   :: () + self → String + self(s)
+let s:parens={'(': ')', '[': ']', '{': '}'}
+let s:revparens={}
+call map(copy(s:parens), 'extend(s:revparens, {v:val : v:key})')
+function s:F.readexpr()
+    if !empty(self.ungot)
+        call self.throw('int', 'fltungetc')
+    endif
+    call self.delblanks()
+    let c=''
+    let parens=[]
+    while !empty(self.s)
+        let chunk=matchstr(self.s, '\v^.{-}[''[\](){}"]')
+        let stopsym=''
+        if empty(chunk)
+            let chunk=self.s
+        else
+            let stopsym=chunk[-1:]
+        endif
+        if has_key(s:parens, stopsym)
+            call add(parens, s:parens[stopsym])
+        elseif has_key(s:revparens, stopsym)
+            let close=''
+            while !empty(parens) && parens[-1] isnot stopsym
+                let close.=remove(parens, -1)
+            endwhile
+            let c.=close
+            if empty(parens)
+                break
+            else
+                call remove(parens, -1)
+            endif
+        elseif stopsym is '"'
+            let string=matchstr(self.s, '\v(\\.|[^\\"])*"', len(chunk))
+            if empty(string)
+                call self.throw('unmatchp', '"')
+            else
+                let chunk.=string
+            endif
+        elseif stopsym is "'"
+            let string=matchstr(self.s, '\v(''''|[^''])*''', len(chunk))
+            if empty(string)
+                call self.throw('unmatchp', "'")
+            else
+                let chunk.=string
+            endif
+        endif
+        call self.removestr(len(chunk))
+        let c.=chunk
+    endwhile
+    let c.=join(parens, '')
+    if empty(c)
+        call self.throw('noexpr')
+    endif
+    return c
+endfunction
+"▶1 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).
+" Input: {<farg>} ( "," {<farg>} )*
+"      | "(" ( {<farg>} ","? )* ")"?
+" Output: context({<farg>}*)
+function s:F.scanlist(farg)
+    call self.addcon()
+    if self.len
+        let c=self.readc()
+        if c is '('
+            while self.len
+                let c=self.readc()
+                if c is ')'
+                    break
+                elseif c isnot ','
+                    call self.ungetc(c)
+                    call call(self['get'.a:farg], [], self)
+                endif
+            endwhile
+        else
+            call self.ungetc(c)
+            while self.len
+                call call(self['get'.a:farg], [], self)
+                if self.len
+                    let c=self.readc()
+                    if c isnot ','
+                        call self.ungetc(c)
+                        break
+                    endif
+                endif
+            endwhile
+        endif
+    endif
+    return self.conclose()
+endfunction
+"▶1 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
+" Input: {funcname} [arguments]?
+"        {funcname} :: {wordchar}+
+" Output: context(intfunc, {funcname}[, contexts])
+function s:F.intfunc()
+    let type=self.stack[-1][0]
+    let c=self.readc()
+    let func=s:F.getmatch(s:args[type], c)
+    if func is 0 "▶2
+        call self.throw('ukfunc', c)
+    endif        "▲2
+    call self.addcon('intfunc', func)
+    let fargs=get(s:args[type], func, [])
+    for farg in fargs
+        if farg[0] is '?'
+            let farg=farg[1:]
+        elseif !self.len "▶2
+            call self.throw('argmis', type.'.'.func)
+        endif            "▲2
+        if farg[0] is '*'
+            call self.scanlist(farg[1:])
+        else
+            call call(self['get'.farg], [], self)
+        endif
+    endfor
+    return self.conclose()
+endfunction
+"▶1 getarg     :: &self!
+" Simple wrapper to scan()
+function s:F.getarg()
+    return self.scan()
+endfunction
+"▶1 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.
+" Input: ( "0" | "1" )?
+" Output: add(0|1)
+function s:F.getone()
+    if !self.len
+        call self.add(0)
+    else
+        let c=self.readc()
+        if c is '1'
+            call self.add(1)
+        else
+            call self.add(0)
+            if c isnot '0'
+                call self.ungetc(c)
+            endif
+        endif
+    endif
+    return self
+endfunction
+"▶1 gettype    :: &self!
+" Adds type number to the context.
+" Valid arguments: s[tring], n[umber], f[loat], d[ictionary], l[ist], fu[nction]
+"                   "", '',     -0,      .0,         {},        []
+" Input: {typedescr}
+" Output: add({typeNumber})
+"         {typeNumber}: any number described in |type()|
+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.gettype()
+    if !self.len
+        call self.throw('typemis')
+    endif
+    let c=self.readc()
+    if has_key(s:typechars, c)
+        call self.add(s:typechars[c][0])
+        if self.len
+            let readchar=s:typechars[c][1]
+            let c=self.readc()
+            if c isnot readchar
+                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
+"▶1 getstring  :: &self!
+" Gets either a string or variable name
+" Input: {str}
+"      | "$" {var}
+"      | {wordchar}+
+"      | .
+" Output: add({string}) | *getvar
+function s:F.getstring()
+    let c=self.readc()
+    if c is '"'
+        call self.add(self.readstr())
+    elseif c is '$'
+        call self.getvar()
+    elseif c=~#'^\w'
+        call self.add(c)
+    else
+        call self.add('').ungetc(c)
+    endif
+    return self
+endfunction
+"▶1 getpath    :: &self!
+" Gets path specification:
+" Input: [( "d" | "f" )] [ "r" ] [( "w" | "W" | "p" )] [ "x" ]
+"        & ! ( "d" ) "r"
+"        & ! ( "d" ) [( "w" | "W" | "p" )] "x"
+" 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)
+" Output: add({pathspec})
+function s:F.getpath()
+    let c=self.readc()
+    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
+"▶1 getsubscr  :: &self!
+" Input: ( "." ( "."? ( ":" {n} {n} | {subscript} ) )* )
+"        {subscript} :: {str}
+"                     | [0-9] {wordchar}*
+"                     | {wordchar}+
+"                {n} :: "-"? {wordchar}+
+" Output: add((String|Number|context(Number, Number))*)
+function s:F.getsubscr()
+    while self.len
+        let c=self.readc()
+        if c is '"'
+            call self.add(self.readstr())
+        elseif c=~#'^\d'
+            call self.add(+c)
+        elseif c=~#'^\w'
+            call self.add(c)
+        elseif c is ':'
+            call self.addcon()
+            " Start and end subscripts
+            for i in range(0, 1)
+                if !self.len
+                    call self.throw('subsmis')
+                endif
+                let v=self.readc()
+                if v is '-'
+                    if !self.len
+                        call self.throw('nonum')
+                    endif
+                    let v.=self.readc()
+                endif
+                call self.add(+v)
+            endfor
+            call self.conclose()
+        elseif c isnot '.'
+            call self.ungetc(c)
+            break
+        endif
+    endwhile
+    return self
+endfunction
+"▶1 getddescr  :: &self
+" Gets dictionary description:
+" Input: "{" ({keydescr} {arg})* "}"
+"        {keydescr} :: {str}
+"                    | "/" {reg}(endstr=/)
+"                    | {arg}
+" Output: context(ddescr, [{keycon}])
+"         {keycon} :: context(eq,    String, {arg})
+"                   | context(regex, String, {arg})
+"                   | context(check, {arg},  {arg})
+function s:F.getddescr()
+    call self.addcon('ddescr')
+    if self.len
+        let c=self.readc()
+        if c isnot '{'
+            call self.ungetc(c)
+        endif
+        let prevlen=-1
+        while self.len && self.len!=prevlen
+            let prevlen=self.len
+            let c=self.readc()
+            if c is '}'
+                break
+            elseif c is '"'
+                call self.addcon('eq', self.readstr()).scan().conclose()
+            elseif c is '/'
+                call self.addcon('regex', self.readreg('/')).scan().conclose()
+            else
+                call self.addcon('check').ungetc(c).scan().scan().conclose()
+            endif
+        endwhile
+    endif
+    return self.conclose()
+endfunction
+"▶1 getreg     :: &self
+" Gets regular expression
+" Input: "$" {var}
+"      | {startchar} {reg}(endchar={endchar})
+"           {startchar} :: ! ( {wordchar} | "$" )
+"             {endchar} :: pair({startchar}) | {startchar}
+" Output: context(regex, {var}|String)
+let s:pairs={
+            \'(': ')',
+            \'[': ']',
+            \'{': '}',
+            \'<': '>',
+        \}
+function s:F.getreg()
+    call self.addcon('regex')
+    let c=self.readc()
+    if c is '$'
+        call self.getvar()
+    else
+        call self.add(self.readreg(get(s:pairs, c, c)))
+    endif
+    return self.conclose()
+endfunction
+"▶1 getnumber  :: &self
+" Get integer or floating point number (including ±inf and nan)
+" Input: {flt}
+"      | "$" {var}
+"      | ""
+" Output: context(inf, "+" | "-")
+"       | context(nan)
+"       | context(number, Number)
+"       | context(float, Float)
+"       | *getvar
+function s:F.getnumber()
+    let f=self.readflt()
+    if f is 0
+        let c=self.readc()
+        if c is '$'
+            return self.getvar()
+        else
+            call self.ungetc(c)
+        endif
+    elseif f[-3:] is 'inf'
+        let sign=f[0]
+        if f[0] is 'i'
+            let sign='+'
+        endif
+        return self.addcon('inf', sign).conclose()
+    elseif f is 'nan'
+        return self.addcon('nan').conclose()
+    endif
+    let r=eval(f)
+    if type(r) is type(0)
+        return self.addcon('number', r).conclose()
+    else
+        return self.addcon('float', r).conclose()
+    endif
+endfunction
+"▶1 getexpr    :: &self
+" Input: {expr}
+" Output: context(expr, String)
+function s:F.getexpr()
+    return self.addcon('expr', self.readexpr()).conclose()
+endfunction
+"▶1 getchvar   :: &self
+" Input: "<"* {subscr}?
+" Output: context(this, Number, {subscr}?)
+function s:F.getchvar()
+    call self.addcon('this', 0)
+    if self.len
+        let c=self.readc()
+        if c is '<'
+            let self.l[-1]+=1
+            while self.len
+                let c=self.readc()
+                if c is '<'
+                    let self.l[-1]+=1
+                else
+                    break
+                endif
+            endwhile
+        endif
+        if c is '.'
+            call self.getsubscr()
+        else
+            call self.ungetc(c)
+        endif
+    endif
+    return self.conclose()
+endfunction
+"▶1 getlist    :: &self
+" Input: ( "$" {var} | "*" {func} | "=" {expr} | {str} | {wordchar}+ )* "]"?
+" Output: context(list, ({var}|{expr}|String)*)
+function s:F.getlist()
+    call self.addcon('list')
+    while self.len
+        let c=self.readc()
+        if c is ']'
+            break
+        elseif c is '$'
+            call self.getvar()
+        elseif c is '='
+            call self.getexpr()
+        elseif c is '*'
+            call self.getfunc()
+        elseif c is '"'
+            call self.add(self.nextstr())
+        elseif c=~#'^\w'
+            call self.add(c)
+        endif
+    endwhile
+    return self.conclose()
+endfunction
+"▶1 getvar     :: &self
+" Input: {wordchar}+ {subscr}*
+"      | "@" {chvar}
+"      | "=" {expr}
+"      | "[" {list}
+"      | "$" {var}
+"      | {str}
+" Output: context(plugvar, String, {subscr}*)
+"       | {chvar}
+"       | {expr}
+"       | {list}
+"       | context(evaluate, {var})
+"       | context(string, String)
+function s:F.getvar()
+    let c=self.readc()
+    if c=~#'^\w'
+        call self.addcon('plugvar', c).getsubscr().conclose()
+    elseif c is '@'
+        return self.getchvar()
+    elseif c is '='
+        return self.getexpr()
+    elseif c is '['
+        call self.getlist()
+    elseif c is '$'
+        call self.addcon('evaluate').getvar().conclose()
+    elseif c is '"'
+        call self.addcon('string', self.readstr()).conclose()
+    else
+        call self.throw('invvar', c)
+    endif
+    return self
+endfunction
+"▶1 getfunc    :: &self
+" Input: {var} ( "(" ( "$" {var} | "*" {func} | {wordchar}+ ) ")"? )?
+" Output: context(func, {var}, ({var}|{func}|context(string, String))*)
+function s:F.getfunc()
+    call self.addcon('func').getvar()
+    if self.len
+        let c=self.readc()
+        if c is '('
+            while self.len
+                let c=self.readc()
+                if c is ')'
+                    break
+                elseif c is '$'
+                    call self.getvar()
+                elseif c is '*'
+                    call self.getfunc()
+                elseif c=~#'^\w'
+                    call self.addcon('string', c).conclose()
+                else
+                    call self.throw('ukfarg', c)
+                endif
+            endwhile
+        else
+            call self.ungetc(c)
+        endif
+    endif
+    return self.conclose()
+endfunction
+"▶1 getmatcher :: &self
+" Input: {intfunc}
+" Output: context(matcher, {intfunc})
+function s:F.getmatcher()
+    return self.addcon('matcher').intfunc().conclose()
+endfunction
+"▶1 scanfie    :: contextName + self → self + self
+" Input: {intfunc}
+"      | "*" {func}
+"      | "=" {expr}
+" Output: context(<contextName>, {intfunc}|{func}|{expr})
+function s:F.scanfie(cname)
+    call self.addcon(a:cname)
+    let c=self.readc()
+    if c is '*'
+        call self.getfunc()
+    elseif c is '='
+        call self.getexpr()
+    else
+        call self.ungetc(c).intfunc()
+    endif
+    return self.conclose()
+endfunction
+"▶1 scanmsg    :: &self
+" Input: {wordchar}+ ( "(" ( "." | "%" | {var} ) ")"? )?
+" Output: context(msg, String, {msgarg}*)
+"         {msgarg} :: context(curval)
+"                   | context(curarg)
+"                   | {var}
+function s:F.scanmsg()
+    call self.addcon('msg', self.readc())
+    if self.len
+        let c=self.readc()
+        if c is '('
+            while self.len
+                let c=self.readc()
+                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
+"▶1 scanopt    :: &self
+" Input: ( ( ":" {var} )? {arg} )* "]"?
+" Output: context(optional, ({arg}|context(default, {var}, {arg}))*)
+function s:F.scanopt()
+    call self.addcon('optional')
+    let prevlen=-1
+    while self.len && self.len!=prevlen
+        let prevlen=self.len
+        let c=self.readc()
+        if c is ']'
+            break
+        elseif c is ':'
+            call self.addcon('default').getvar().scan().conclose()
+        else
+            call self.ungetc(c).scan()
+        endif
+    endwhile
+    return self.conclose()
+endfunction
+"▶1 scanpref   :: &self
+" Input: ({prefdescr} ( {arg} | "-" ) )* "}"?
+"        {prefdescr} :: ( "?" | "*" )* ( ":" {var} )? ({str} | {wordchar}+)
+" Output: context(prefixes, context(String, {prefopts}[, {var}][, {arg}]))
+"         {prefopts} :: { "alt": Bool, "list": Bool }
+let s:defprefopts={'alt': 0, 'list': 0}
+function s:F.scanpref()
+    call self.addcon('prefixes')
+    let c=self.readc()
+    if c is '~'
+        call self.getmatcher()
+    else
+        call self.ungetc(c)
+    endif
+    let prevlen=-1
+    while self.len && prevlen!=self.len
+        let prevlen=self.len
+        let c=self.readc()
+        if !exists('pref')
+            if !exists('prefopts')
+                let prefopts=copy(s:defprefopts)
+            endif
+            if c is '"'
+                let c=self.readstr()
+                let pref=c
+            elseif c=~#'^\w'
+                let pref=c
+            elseif c is '?'
+                let prefopts.alt=1
+            elseif c is '*'
+                let prefopts.list=1
+            elseif c is '}'
+                break
+            else
+                call self.ungetc(c)
+                break
+            endif
+        else
+            call self.addcon('prefix', pref, prefopts)
+            unlet pref prefopts
+            if c is ':'
+                call self.getvar()
+            elseif c is '-'
+                continue
+            else
+                call self.ungetc(c)
+            endif
+            call self.scan().conclose()
+        endif
+    endwhile
+    return self.conclose()
+endfunction
+"▶1 scanact    :: &self
+" Input: "~" {matcher} ( {actdescr} ( "-" | "(" {arg}* ")"? | {arg} ) )* ">"?
+"        {actdescr} :: {str}
+"                    | {wordchar}+
+" Output: context(actions[, {matcher}],
+"                 (context(action, 0|String|{arg}, {arg}*))
+function s:F.scanact()
+    call self.addcon('actions')
+    let c=self.readc()
+    if c is '~'
+        call self.getmatcher()
+    else
+        call self.ungetc(c)
+    endif
+    let hasaction=0
+    let prevlen=-1