├── autoload ├── vital │ ├── powerassert.vital │ ├── _powerassert.vim │ ├── _powerassert │ │ ├── Vim │ │ │ ├── SafeString.vim │ │ │ └── VimlCompiler.vim │ │ └── Data │ │ │ ├── List.vim │ │ │ └── String.vim │ ├── powerassert.vim │ └── __powerassert__ │ │ └── Vim │ │ └── PowerAssert.vim ├── powerassert.vim └── vital.vim ├── .vintrc.yaml ├── test ├── install-vim.sh ├── Example.vimspec ├── .themisrc ├── command.vimspec ├── TODO.vimspec ├── config.vimspec └── PowerAssert.vimspec ├── LICENSE ├── appveyor.yml ├── .travis.yml ├── README.md └── doc └── vital-power-assert.txt /autoload/vital/powerassert.vital: -------------------------------------------------------------------------------- 1 | powerassert 2 | bff0d8c58c1fb6ab9e4a9fc0c672368502f10d88 3 | 4 | Vim.PowerAssert 5 | -------------------------------------------------------------------------------- /.vintrc.yaml: -------------------------------------------------------------------------------- 1 | # Config for vint 2 | # https://github.com/Kuniwak/vint 3 | policies: 4 | ProhibitImplicitScopeVariable: 5 | enabled: false 6 | -------------------------------------------------------------------------------- /autoload/vital/_powerassert.vim: -------------------------------------------------------------------------------- 1 | let s:_plugin_name = expand(':t:r') 2 | 3 | function! vital#{s:_plugin_name}#new() abort 4 | return vital#{s:_plugin_name[1:]}#new() 5 | endfunction 6 | -------------------------------------------------------------------------------- /autoload/powerassert.vim: -------------------------------------------------------------------------------- 1 | let s:PowerAssert = vital#powerassert#new().import('Vim.PowerAssert') 2 | 3 | function! powerassert#import() abort 4 | return s:PowerAssert 5 | endfunction 6 | 7 | function! powerassert#assert(...) abort 8 | return call(s:PowerAssert.assert, a:000, {}) 9 | endfunction 10 | 11 | function! powerassert#define(...) abort 12 | return call(s:PowerAssert.define, a:000, {}) 13 | endfunction 14 | -------------------------------------------------------------------------------- /autoload/vital.vim: -------------------------------------------------------------------------------- 1 | function! vital#of(name) abort 2 | let files = globpath(&runtimepath, 'autoload/vital/' . a:name . '.vital', 1) 3 | let file = split(files, "\n") 4 | if empty(file) 5 | throw 'vital: version file not found: ' . a:name 6 | endif 7 | let ver = readfile(file[0], 'b') 8 | if empty(ver) 9 | throw 'vital: invalid version file: ' . a:name 10 | endif 11 | return vital#_{substitute(ver[0], '\W', '', 'g')}#new() 12 | endfunction 13 | -------------------------------------------------------------------------------- /test/install-vim.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # REF: https://github.com/vim-jp/vital.vim/blob/master/install-vim.sh 3 | set -e 4 | if [ x"$HEAD" = "xyes" ]; then 5 | git clone --depth 1 https://github.com/vim-jp/vim /tmp/vim 6 | cd /tmp/vim 7 | ./configure --prefix="$HOME/vim" --with-features=huge \ 8 | --enable-perlinterp --enable-pythoninterp --enable-python3interp \ 9 | --enable-rubyinterp --enable-luainterp --enable-fail-if-missing 10 | make -j2 11 | make install 12 | fi 13 | -------------------------------------------------------------------------------- /test/Example.vimspec: -------------------------------------------------------------------------------- 1 | Describe Example 2 | Before all 3 | let V = vital#vital#new() 4 | let PowerAssert = V.import('Vim.PowerAssert') 5 | let s:assert = PowerAssert.assert 6 | execute PowerAssert.define('PowerAssert') 7 | End 8 | 9 | It throw exception with descriptive graphical message 10 | Skip 'sample' 11 | let x = { 'ary': [1, 2, 3], 'power': 'assert' } 12 | let l:zero = 0 13 | let s:two = 2 14 | PowerAssert index(x.ary, l:zero) is# s:two 15 | " or 16 | execute s:assert('index(x.ary, l:zero) is# s:two') 17 | End 18 | End 19 | -------------------------------------------------------------------------------- /test/.themisrc: -------------------------------------------------------------------------------- 1 | call themis#option('recursive', 1) 2 | 3 | " XXX: For development 4 | call themis#option('runtimepath', expand('~/.vim/bundle/vital.vim')) 5 | call themis#option('runtimepath', expand('~/.vim/bundle/vital-vimlcompiler')) 6 | call themis#option('runtimepath', expand('~/.vim/bundle/vital-safe-string')) 7 | 8 | let g:Expect = themis#helper('expect') 9 | call themis#helper('command').with(themis#helper('assert')).with({'Expect': g:Expect}) 10 | 11 | let g:__vital_power_assert_config = { 12 | \ '__debug__': 1, 13 | \ '__pseudo_throw__': 0, 14 | \ '__max_length__': -1 15 | \ } 16 | 17 | language C 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015 haya14busa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 20 | THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/command.vimspec: -------------------------------------------------------------------------------- 1 | Describe command 2 | 3 | Before all 4 | let g:PowerAssert = vital#vital#import('Vim.PowerAssert') 5 | " to see script-local scope 6 | execute PowerAssert.define('AssertCmd') 7 | End 8 | 9 | Describe .define() 10 | It should define command with given command name 11 | execute PowerAssert.define('CommandTest') 12 | Assert Exists(':CommandTest') 13 | delcommand CommandTest 14 | End 15 | 16 | It does nothing if given expression is true 17 | AssertCmd 1 == 1 18 | End 19 | 20 | It should support double quote 21 | let x = 'foo' 22 | AssertCmd x ==# 'foo' 23 | End 24 | 25 | It should support single quote 26 | let x = 'foo' 27 | AssertCmd x ==# "foo" 28 | End 29 | 30 | It throws abort exception with falsy expression 31 | Throws /vital: PowerAssert:/ :AssertCmd 2 == 1 32 | End 33 | 34 | It command can access caller scope variables 35 | let s:x = 'xxx' 36 | let l:xxx = 'y' 37 | let b:x = 'zzz' 38 | let t:x = 'ttt' 39 | AssertCmd s:x != l:xxx && b:x != t:x 40 | Throws /vital: PowerAssert:/ :AssertCmd s:x == l:xxx || b:x == t:x 41 | unlet b:x s:x t:x 42 | End 43 | End 44 | 45 | End 46 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | build: off 3 | deploy: off 4 | skip_tags: true 5 | clone_depth: 1 6 | environment: 7 | matrix: 8 | - VIM_URL: http://files.kaoriya.net/vim/vim74-kaoriya-win64.zip 9 | - VIM_URL: http://files.kaoriya.net/vim/2011/vim73-kaoriya-win64-20110306.zip 10 | install: 11 | - git clone --quiet https://github.com/thinca/vim-themis --branch v1.4.1 --single-branch --depth 1 themis 12 | - git clone --quiet https://github.com/vim-jp/vital.vim.git vital 13 | - git clone --quiet https://github.com/haya14busa/vital-vimlcompiler vital-vimlcompiler 14 | - git clone --quiet https://github.com/haya14busa/vital-safe-string vital-safe-string 15 | - ps: | 16 | $zip = $Env:APPVEYOR_BUILD_FOLDER + '\vim.zip' 17 | $vim = $Env:APPVEYOR_BUILD_FOLDER + '\vim\' 18 | (New-Object Net.WebClient).DownloadFile($Env:VIM_URL, $zip) 19 | [Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') > $null 20 | [System.IO.Compression.ZipFile]::ExtractToDirectory($zip, $vim) 21 | $Env:THEMIS_VIM = $vim + (Get-ChildItem $vim).Name + '\vim.exe' 22 | before_test: 23 | - ps: | 24 | & $Env:THEMIS_VIM --version 25 | test_script: 26 | - ps: | 27 | .\themis\bin\themis.bat -r --runtimepath vital --runtimepath vital-vimlcompiler --runtimepath vital-safe-string --reporter spec 28 | -------------------------------------------------------------------------------- /test/TODO.vimspec: -------------------------------------------------------------------------------- 1 | Describe TODO 2 | 3 | Before all 4 | let PowerAssert = vital#vital#import('Vim.PowerAssert') 5 | let s:assert = PowerAssert.assert 6 | End 7 | 8 | It should inspect nodes regardless exception from other evaluations 9 | Skip 'not implemented yet' 10 | let x = {} 11 | " E15 12 | execute s:assert('0 == {}') 13 | End 14 | 15 | It should inspect nodes in 'string' arg for `map()` 16 | Skip 'not implemented yet' 17 | let x = 2 18 | execute s:assert('[] == map([1, 2, 3], "v:val * x")') 19 | End 20 | 21 | It should inspect nodes in 'string' arg for `filter()` 22 | Skip 'not implemented yet' 23 | let x = 2 24 | execute s:assert('[] == filter([1, 2, 3], "v:val % x")') 25 | End 26 | 27 | It should not inspect `v:val` in 'string' arg for `map()`/ `filter()` 28 | Skip 'not implemented yet' 29 | execute s:assert('[] == map([1, 2, 3], "v:val * v:val")') 30 | End 31 | 32 | It should output diff between left and right string of binary operaration 33 | Skip 'not implemented yet' 34 | let x = 'vim' 35 | let y = 'Vim' 36 | execute s:assert('x is# y') 37 | End 38 | 39 | It should inspect right node of dot node if the dot means string concatenation 40 | Skip 'not implemented yet' 41 | let x = 'vim' 42 | let y = 'Vim' 43 | execute s:assert('x.y == "hi"') 44 | End 45 | 46 | It should accept and output additional message to assert 47 | Skip 'not implemented yet' 48 | execute s:assert('x.y == "hi"', 'additional message') 49 | End 50 | End 51 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | env: 3 | - HEAD=yes 4 | - HEAD=no 5 | sudo: false 6 | cache: pip 7 | 8 | addons: 9 | apt: 10 | packages: 11 | - language-pack-ja 12 | - vim 13 | - libperl-dev 14 | - python-dev 15 | - python3-dev 16 | - liblua5.1-0-dev 17 | - lua5.1 18 | 19 | install: 20 | # Vim 21 | - bash ./test/install-vim.sh 22 | - if [ x"$HEAD" = "xyes" ]; then export PATH=$HOME/vim/bin:$PATH; fi 23 | # vint 24 | - pip install vim-vint --user 25 | - export PATH=$HOME/.local/bin:$PATH 26 | # vimlint 27 | - git clone https://github.com/syngan/vim-vimlint /tmp/vim-vimlint 28 | - git clone https://github.com/ynkdir/vim-vimlparser /tmp/vim-vimlparser 29 | # themis 30 | - git clone https://github.com/thinca/vim-themis --branch v1.5 --single-branch --depth 1 /tmp/vim-themis 31 | # testing helper 32 | - git clone https://github.com/vim-jp/vital.vim /tmp/vital.vim # for testing if required 33 | - git clone https://github.com/haya14busa/vital-vimlcompiler /tmp/vital-vimlcompiler 34 | - git clone https://github.com/haya14busa/vital-safe-string /tmp/vital-safe-string 35 | 36 | before_script: 37 | - vim --version 38 | - vint --version 39 | 40 | script: 41 | - which -a vim 42 | - vim --cmd version --cmd quit 43 | - if [ -e doc/ ]; then vim --cmd 'try | helptags doc/ | catch | cquit | endtry' --cmd 'quit'; fi 44 | - sh /tmp/vim-vimlint/bin/vimlint.sh -l /tmp/vim-vimlint -p /tmp/vim-vimlparser -e EVL103=1 -e EVL102.l:_=1 -c func_abort=1 ./autoload/vital/__powerassert__/Vim/PowerAssert.vim 45 | - vint --color ./autoload/vital/__powerassert__/Vim/PowerAssert.vim 46 | - /tmp/vim-themis/bin/themis --runtimepath /tmp/vital.vim --runtimepath /tmp/vital-vimlcompiler --runtimepath /tmp/vital-safe-string --reporter spec 47 | -------------------------------------------------------------------------------- /test/config.vimspec: -------------------------------------------------------------------------------- 1 | Describe config 2 | 3 | Before all 4 | let g:PowerAssert = vital#vital#import('Vim.PowerAssert') 5 | let s:assert = g:PowerAssert.assert 6 | End 7 | 8 | Describe g:__vital_power_assert_config 9 | It should work without this variable 10 | let _save = get(g:, '__vital_power_assert_config', {}) 11 | try 12 | let x = 'vim' 13 | execute s:assert('x != "vi"') 14 | Throws /vital: PowerAssert:/ :execute g:PowerAssert.assert('x == "vi"') 15 | finally 16 | let g:__vital_power_assert_config = _save 17 | endtry 18 | End 19 | 20 | It should not throw exception for falsy one if __debug__ option is off 21 | let _save = get(g:, '__vital_power_assert_config', {}) 22 | try 23 | for pseudo_option in [0, 1] 24 | let g:__vital_power_assert_config = { 25 | \ '__debug__': 0, 26 | \ '__pseudo_throw__': pseudo_option 27 | \ } 28 | execute s:assert('x != "vi"') 29 | execute s:assert('x == "vi"') 30 | endfor 31 | finally 32 | let g:__vital_power_assert_config = _save 33 | endtry 34 | End 35 | 36 | It should throw abort exception with __pseudo_throw__ is true 37 | let _save = get(g:, '__vital_power_assert_config', {}) 38 | let g:__vital_power_assert_config = { 39 | \ '__debug__': 1, 40 | \ '__pseudo_throw__': 1 41 | \ } 42 | try 43 | let x = 'vim' 44 | execute s:assert('x != "vi"') 45 | Throws /^vital: PowerAssert: abort$/ :execute g:PowerAssert.assert('x == "vi"') 46 | finally 47 | let g:__vital_power_assert_config = _save 48 | endtry 49 | End 50 | 51 | It should throw exception with __pseudo_throw__ is false 52 | let _save = get(g:, '__vital_power_assert_config', {}) 53 | let g:__vital_power_assert_config = { 54 | \ '__debug__': 1, 55 | \ '__pseudo_throw__': 0 56 | \ } 57 | try 58 | let x = 'vim' 59 | execute s:assert('x != "vi"') 60 | Throws /^vital: PowerAssert:\n/ :execute g:PowerAssert.assert('x == "vi"') 61 | finally 62 | let g:__vital_power_assert_config = _save 63 | endtry 64 | End 65 | End 66 | 67 | End 68 | -------------------------------------------------------------------------------- /autoload/vital/_powerassert/Vim/SafeString.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | if v:version > 703 || v:version == 703 && has('patch1170') 5 | function! vital#_powerassert#Vim#SafeString#import() abort 6 | return map({'string': '', '_vital_loaded': ''}, 'function("s:" . v:key)') 7 | endfunction 8 | else 9 | function! s:_SID() abort 10 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 11 | endfunction 12 | execute join(['function! vital#_powerassert#Vim#SafeString#import() abort', printf("return map({'string': '', '_vital_loaded': ''}, \"function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 13 | delfunction s:_SID 14 | endif 15 | " ___vital___ 16 | " @vimlint(EVL103, 1, a:in) 17 | " @vimlint(EVL103, 1, a:out) 18 | 19 | function! s:_vital_loaded(V) abort 20 | if get(g:, 'vital_vim_save_string_debug', 0) 21 | \ && a:V.exists('Vim.PowerAssert') 22 | \ && a:V.exists('Vim.DbC') 23 | execute a:V.import('Vim.PowerAssert').define('PowerAssert') 24 | let s:assert = a:V.import('Vim.PowerAssert').assert 25 | execute a:V.import('Vim.DbC').dbc() 26 | endif 27 | endfunction 28 | 29 | " s:string() convert {expr} to string with nested element support. 30 | " @param {Any} expr 31 | " @return {string} 32 | function! s:string(expr) abort 33 | if type(a:expr) is# type([]) 34 | return printf('[%s]', join(map(copy(a:expr), 's:string(v:val)'), ', ')) 35 | elseif type(a:expr) is# type({}) 36 | return s:_dict_to_string(a:expr) 37 | else 38 | return string(a:expr) 39 | endif 40 | endfunction 41 | 42 | function! s:__pre_string(in) abort 43 | endfunction 44 | 45 | function! s:__post_string(in, out) abort 46 | PowerAssert type(a:out) is# type('') 47 | if type(a:in.expr) isnot# type([]) && type(a:in.expr) isnot# type({}) 48 | PowerAssert eval(a:out) is# a:in.expr, '{out} should be parsed back with eval()' 49 | endif 50 | if type(a:in.expr) isnot# type('') && type(a:in.expr) isnot# type(function('function')) 51 | exe s:assert('a:out is# s:_echo_capture(a:in.expr)', '{out} should be same as `:echo`-ing {expr}') 52 | " XXX: cannot refer s:_echo_capture() ??? 53 | " PowerAssert a:out is# s:_echo_capture(a:in.expr), '{out} should be same as `:echo`-ing {expr}' 54 | endif 55 | endfunction 56 | 57 | function! s:_dict_to_string(dict, ...) abort 58 | " `seen` cannot be dict because we cannot hash nested dict! 59 | let seen = get(a:, 1, []) 60 | let seen += [a:dict] 61 | let pairs = [] 62 | for [k, l:V] in items(a:dict) 63 | if type(l:V) is# type({}) 64 | if index(seen, l:V) !=# -1 65 | let v_str = '{...}' 66 | else 67 | let v_str = s:_dict_to_string(l:V, seen) 68 | endif 69 | else 70 | let v_str = s:string(l:V) 71 | endif 72 | let pairs += [printf('%s: %s', string(k), v_str)] 73 | unlet l:V 74 | endfor 75 | return printf('{%s}', join(pairs, ', ')) 76 | endfunction 77 | 78 | function! s:__pre__dict_to_string(in) abort 79 | PowerAssert type(a:in.dict) is# type({}) 80 | endfunction 81 | 82 | function! s:__post__dict_to_string(in, out) abort 83 | PowerAssert type(a:out) is# type('') 84 | endfunction 85 | 86 | function! s:_echo_capture(expr) abort 87 | " @vimlint(EVL102, 1, l:Expr) 88 | let l:Expr = a:expr 89 | try 90 | let save_verbose = &verbose 91 | let &verbose = 0 92 | redir => out 93 | silent execute ':echo l:Expr' 94 | finally 95 | redir END 96 | let &verbose = save_verbose 97 | endtry 98 | return out[1:] 99 | endfunction 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :muscle: vital-power-assert :muscle: 2 | ==================================== 3 | 4 | [![Build Status](https://travis-ci.org/haya14busa/vital-power-assert.svg?branch=master)](https://travis-ci.org/haya14busa/vital-power-assert) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/ljx7q8a9xe58k022?svg=true)](https://ci.appveyor.com/project/haya14busa/vital-power-assert) 6 | [![](https://img.shields.io/github/tag/haya14busa/vital-power-assert.svg)](https://github.com/haya14busa/vital-power-assert/releases) 7 | [![](https://img.shields.io/github/issues/haya14busa/vital-power-assert.svg)](https://github.com/haya14busa/vital-power-assert/issues) 8 | [![](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 9 | [![](https://img.shields.io/badge/doc-%3Ah%20vital--power--assert.txt-red.svg)](doc/vital-power-assert.txt) 10 | ![vital-power-assert.png (862×622)](https://raw.githubusercontent.com/haya14busa/i/master/vital-power-assert/vital-power-assert.png) 11 | 12 | [haya14busa/vital-power-assert](https://github.com/haya14busa/vital-power-assert) 13 | provides descriptive assertion messages with assertion function or :command. 14 | 15 | ```vim 16 | let s:V = vital#vital#new() 17 | let s:PowerAssert = s:V.import('Vim.PowerAssert') 18 | let s:assert = s:PowerAssert.assert 19 | execute s:PowerAssert.define('PowerAssert') 20 | function! s:power_assert() abort 21 | let x = { 'ary': [1, 2, 3], 'power': 'assert' } 22 | let l:zero = 0 23 | let s:two = 2 24 | PowerAssert index(x.ary, l:zero) is# s:two 25 | " or 26 | execute s:assert('index(x.ary, l:zero) is# s:two') 27 | endfunction 28 | call s:power_assert() 29 | ``` 30 | 31 | => 32 | 33 | ```txt 34 | vital: PowerAssert: 35 | index(x.ary, l:zero) is# s:two 36 | ||| | | | 37 | ||| | | 2 38 | ||| | 0 39 | ||| 0 40 | ||[1, 2, 3] 41 | |{'ary': [1, 2, 3], 'power': 'assert'} 42 | -1 43 | ``` 44 | 45 | Installation 46 | ------------ 47 | 48 | ### 1. Install |vital.vim|, vital-vimlcompiler, and |vital-power-assert| with your favorite plugin manager. 49 | 50 | ```vim 51 | NeoBundle 'vim-jp/vital.vim' 52 | NeoBundle 'haya14busa/vital-vimlcompiler' 53 | NeoBundle 'haya14busa/vital-power-assert' 54 | NeoBundle 'haya14busa/vital-safe-string' 55 | 56 | Plugin 'vim-jp/vital.vim' 57 | Plugin 'haya14busa/vital-vimlcompiler' 58 | Plugin 'haya14busa/vital-power-assert' 59 | Plugin 'haya14busa/vital-safe-string' 60 | 61 | Plug 'vim-jp/vital.vim' 62 | Plug 'haya14busa/vital-vimlcompiler' 63 | Plug 'haya14busa/vital-power-assert' 64 | Plug 'haya14busa/vital-safe-string' 65 | ``` 66 | 67 | ### 2. Embed vital-power-assert into your plugin with |:Vitalize| (assume current directory is the root of your plugin repository). 68 | See |:Vitalize| for more information. 69 | ```vim 70 | :Vitalize . --name={plugin_name} Vim.PowerAssert 71 | ``` 72 | 73 | ### 3. You can update vital-power-assert with |:Vitalize|. 74 | ```vim 75 | :Vitalize . 76 | ``` 77 | 78 | ### 4. Please add following lines in your vimrc. 79 | ```vim 80 | let g:__vital_power_assert_config = { 81 | \ '__debug__': 1 82 | \ } 83 | ``` 84 | 85 | Usage 86 | ----- 87 | 88 | You can assert expression with `function` or `command`. 89 | Both method support assertion with any scope variable or function like s:var and 90 | they does nothing unless `g:__vital_power_assert_config.__debug__` is true. 91 | 92 | ### Function (`.assert()`) 93 | 94 | ```vim 95 | let s:V = vital#vital#new() 96 | let s:PowerAssert = s:V.import('Vim.PowerAssert') 97 | let s:assert = s:PowerAssert.assert 98 | let x = 1 99 | execute s:assert('x == 2') 100 | " => 101 | " vital: PowerAssert: 102 | " x == 2 103 | " | | 104 | " | 0 105 | " 1 106 | ``` 107 | 108 | It execute assertion from given string expression. 109 | If it's true, vital-power-assert does nothing. 110 | If it's false, it throws exception with descriptive graphical message. 111 | 112 | If `g:__vital_power_assert_config.__debug__` is false (default), 113 | this function does nothing even if assertion will fail, so you can leave 114 | assertion lines in production code if you want. 115 | 116 | ### Command (`.define()`) 117 | 118 | ```vim 119 | let s:V = vital#vital#new() 120 | let s:PowerAssert = s:V.import('Vim.PowerAssert') 121 | execute s:PowerAssert.define('PowerAssert') 122 | let x = 1 123 | PowerAssert x == 2 124 | " => 125 | " vital: PowerAssert: 126 | " x == 2 127 | " | | 128 | " | 0 129 | " 1 130 | ``` 131 | 132 | You can define assertion command with `.define()` and you that command like `.assert()`. 133 | It's better than `.assert()` because you don't have to wrap expression with string, but 134 | since it define new command, it annoys your plugin users. 135 | I strongly recommend not to leave assertion command in your plugin. 136 | 137 | NOTE: To handle script-local variables, you have to define command in each file (with different name not to overwrite previous one). 138 | 139 | I recommend to use the assert command in https://github.com/thinca/vim-themis 140 | 141 | ### Use vital-power-assert in themis 142 | 143 | #### .themisrc 144 | 145 | ```vim 146 | call themis#option('runtimepath', expand('~/.vim/bundle/vital.vim')) 147 | call themis#option('runtimepath', expand('~/.vim/bundle/vital-vimlcompiler')) 148 | 149 | let g:__vital_power_assert_config = { 150 | \ '__debug__': 1, 151 | \ '__pseudo_throw__': 0 152 | \ } 153 | ``` 154 | 155 | ### test/Example.vimspec 156 | 157 | ```vim 158 | Describe Example 159 | Before all 160 | let V = vital#vital#new() 161 | let PowerAssert = V.import('Vim.PowerAssert') 162 | execute PowerAssert.define('PowerAssert') 163 | " or 164 | let s:assert = PowerAssert.assert 165 | End 166 | 167 | It throw exception with descriptive graphical message 168 | let x = { 'ary': [1, 2, 3], 'power': 'assert' } 169 | let l:zero = 0 170 | let s:two = 2 171 | PowerAssert index(x.ary, l:zero) is# s:two 172 | " or 173 | execute s:assert('index(x.ary, l:zero) is# s:two') 174 | End 175 | End 176 | ``` 177 | 178 | ### Output 179 | 180 | ``` 181 | # themis --reporter spec test/Example.vimspec 182 | Example 183 | [✖] throw exception with descriptive graphical message 184 | function 114() abort dict Line:5 (/tmp/va1bids/0) 185 | vital: PowerAssert: 186 | index(x.ary, l:zero) is# s:two 187 | ||| | | | 188 | ||| | | 2 189 | ||| | 0 190 | ||| 0 191 | ||[1, 2, 3] 192 | |{'ary': [1, 2, 3], 'power': 'assert'} 193 | -1 194 | 195 | tests 1 196 | passes 0 197 | fails 1 198 | ``` 199 | 200 | Credit 201 | ------ 202 | ### https://github.com/power-assert-js/power-assert 203 | 204 | vital-power-assert is inspired by power-assert-js. 205 | 206 | ### https://github.com/ynkdir/vim-vimlparser 207 | 208 | Vim script parser written in Vim script which vital-power-assert and vital-vimlcompiler use. 209 | 210 | :bird: Author 211 | ------------- 212 | haya14busa (https://github.com/haya14busa) 213 | -------------------------------------------------------------------------------- /doc/vital-power-assert.txt: -------------------------------------------------------------------------------- 1 | *vital-power-assert.txt* assertion library for Vim script 2 | 3 | 4 | Author : haya14busa 5 | Version : 0.9.0 6 | License : MIT license {{{ 7 | 8 | Copyright (c) 2015 haya14busa 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining 11 | a copy of this software and associated documentation files (the 12 | "Software"), to deal in the Software without restriction, including 13 | without limitation the rights to use, copy, modify, merge, publish, 14 | distribute, sublicense, and/or sell copies of the Software, and to 15 | permit persons to whom the Software is furnished to do so, subject to 16 | the following conditions: 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 23 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 24 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 25 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 26 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | 28 | }}} 29 | 30 | ============================================================================== 31 | CONTENTS *Vital.Vim.PowerAssert-contents* 32 | 33 | INTRODUCTION |Vital.Vim.PowerAssert-introduction| 34 | INSTALLATION |Vital.Vim.PowerAssert-installation| 35 | INTERFACE |Vital.Vim.PowerAssert-interface| 36 | FUNCTIONS |Vital.Vim.PowerAssert-functions| 37 | VARIABLES |Vital.Vim.PowerAssert-variables| 38 | Changelog |Vital.Vim.PowerAssert-changelog| 39 | 40 | ============================================================================== 41 | INTRODUCTION *Vital.Vim.PowerAssert-introduction* 42 | 43 | *Vital.Vim.PowerAssert* (*vital-power-assert* , *vital-power-assert.vim* ) 44 | provides descriptive assertion messages with assertion function or :command. 45 | 46 | Assertion: > 47 | let s:V = vital#vital#new() 48 | let s:PowerAssert = s:V.import('Vim.PowerAssert') 49 | let s:assert = s:PowerAssert.assert 50 | execute s:PowerAssert.define('PowerAssert') 51 | function! s:power_assert() abort 52 | let x = { 'ary': [1, 2, 3], 'power': 'assert' } 53 | let l:zero = 0 54 | let s:two = 2 55 | PowerAssert index(x.ary, l:zero) is# s:two 56 | " or 57 | execute s:assert('index(x.ary, l:zero) is# s:two') 58 | endfunction 59 | call s:power_assert() 60 | 61 | 62 | vital: PowerAssert: 63 | index(x.ary, l:zero) is# s:two 64 | ||| | | | 65 | ||| | | 2 66 | ||| | 0 67 | ||| 0 68 | ||[1, 2, 3] 69 | |{'ary': [1, 2, 3], 'power': 'assert'} 70 | -1 71 | < 72 | 73 | ============================================================================== 74 | INSTALLATION *Vital.Vim.PowerAssert-installation* 75 | 76 | 1. Install |vital.vim|, vital-vimlcompiler, and |vital-power-assert| with your 77 | favorite plugin manager. > 78 | NeoBundle 'vim-jp/vital.vim' 79 | NeoBundle 'haya14busa/vital-vimlcompiler' 80 | NeoBundle 'haya14busa/vital-power-assert' 81 | NeoBundle 'haya14busa/vital-safe-string' 82 | 83 | Plugin 'vim-jp/vital.vim' 84 | Plugin 'haya14busa/vital-vimlcompiler' 85 | Plugin 'haya14busa/vital-power-assert' 86 | Plugin 'haya14busa/vital-safe-string' 87 | 88 | Plug 'vim-jp/vital.vim' 89 | Plug 'haya14busa/vital-vimlcompiler' 90 | Plug 'haya14busa/vital-power-assert' 91 | Plug 'haya14busa/vital-safe-string' 92 | < 93 | 2. Embed vital-power-assert into your plugin with |:Vitalize| 94 | (assume current directory is the root of your plugin repository). 95 | See |:Vitalize| for more information. > 96 | :Vitalize . --name={plugin_name} Vim.PowerAssert 97 | < 98 | 3. You can update vital-power-assert with |:Vitalize|. > 99 | :Vitalize . 100 | < 101 | 4. Please add following lines in your vimrc. > 102 | let g:__vital_power_assert_config = { 103 | \ '__debug__': 1 104 | \ } 105 | < 106 | ============================================================================== 107 | INTERFACE *Vital.Vim.PowerAssert-interface* 108 | ------------------------------------------------------------------------------ 109 | FUNCTIONS *Vital.Vim.PowerAssert-functions* 110 | 111 | assert({string} [, {message}]) *Vital.Vim.PowerAssert.assert()* 112 | *powerassert#assert()* 113 | Assert given {string} expression. If it's true, 114 | vital-power-assert does nothing, but if it's false, it 115 | throws exception with descriptive graphical message. 116 | If |Vital.Vim.PowerAssert-variables--debug| is false, 117 | this function does nothing, so you can leave assertion 118 | lines in production code if you want. 119 | 120 | NOTE: Please |:execute| this function instead of |:call|. 121 | Examples: > 122 | let s:V = vital#vital#new() 123 | let s:PowerAssert = s:V.import('Vim.PowerAssert') 124 | let s:assert = s:PowerAssert.assert 125 | let x = 1 126 | execute s:assert('x == 2') 127 | < Message: > 128 | vital: PowerAssert: 129 | x == 2 130 | | | 131 | | 0 132 | 1 133 | < 134 | define({string}) *Vital.Vim.PowerAssert.define()* 135 | *powerassert#define()* 136 | Define assertion command. {string} will be command 137 | name. You can use defined command to assert expression 138 | like |Vital.Vim.PowerAssert.assert()|. 139 | 140 | NOTE: Please |:execute| this function instead of |:call|. 141 | 142 | NOTE: Please define assertion command for each file 143 | if you want to use |s:var| for expression to assert. 144 | Since, this function defines new |:command|, you should 145 | not leave this function and assertion command in 146 | production code. I strongly recommend to use command 147 | assertion only in testing framework like 148 | |themis.vim| [1]. 149 | 150 | [1]: https://github.com/thinca/vim-themis 151 | 152 | Examples: > 153 | let s:V = vital#vital#new() 154 | let s:PowerAssert = s:V.import('Vim.PowerAssert') 155 | execute s:PowerAssert.define('PowerAssert') 156 | let x = 1 157 | PowerAssert x == 2 158 | < Message: > 159 | vital: PowerAssert: 160 | x == 2 161 | | | 162 | | 0 163 | 1 164 | < Examples with message: > 165 | let x = 1 166 | PowerAssert x == 2, 'x should be 2' 167 | < Message: > 168 | vital: PowerAssert: x should be 2 169 | x == 2 170 | | | 171 | | 0 172 | 1 173 | < 174 | ------------------------------------------------------------------------------ 175 | VARIABLES *Vital.Vim.PowerAssert-variables* 176 | *g:__vital_power_assert_config* 177 | 178 | config.__debug__ *Vital.Vim.PowerAssert-variables--debug* 179 | Type: Boolean (0 or 1) 180 | Default: 0 181 | Do not run assertions if it's false. 182 | NOTE: Please add following lines in your vimrc. > 183 | 184 | let g:__vital_power_assert_config = { 185 | \ '__debug__': 1 186 | \ } 187 | 188 | config.__pseudo_throw__ *Vital.Vim.PowerAssert-variables--pseudo_throw* 189 | Type: Boolean (0 or 1) 190 | Default: 1 191 | Do not use |:throw| to output descriptive messages 192 | because you cannot throw message with multiple lines 193 | with |:throw|. 194 | 195 | NOTE: Please add following lines in |.themisrc| to 196 | use |vital-power-assert| with |.themis| [1]. 197 | 198 | [1]: https://github.com/thinca/vim-themis > 199 | 200 | let g:__vital_power_assert_config = { 201 | \ '__debug__': 1, 202 | \ '__pseudo_throw__': 0 203 | \ } 204 | 205 | ============================================================================== 206 | CHANGELOG *Vital.Vim.PowerAssert-changelog* 207 | 208 | 0.9.0 2015-08-19 209 | - Init. 210 | 211 | ============================================================================== 212 | vim:tw=78:ts=8:ft=help:norl:noet:fen:fdl=0:fdm=marker: 213 | -------------------------------------------------------------------------------- /autoload/vital/powerassert.vim: -------------------------------------------------------------------------------- 1 | let s:plugin_name = expand(':t:r') 2 | let s:vital_base_dir = expand(':h') 3 | let s:project_root = expand(':h:h:h') 4 | let s:is_vital_vim = s:plugin_name is# 'vital' 5 | 6 | let s:loaded = {} 7 | let s:cache_sid = {} 8 | 9 | " function() wrapper 10 | if v:version > 703 || v:version == 703 && has('patch1170') 11 | function! s:_function(fstr) abort 12 | return function(a:fstr) 13 | endfunction 14 | else 15 | function! s:_SID() abort 16 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 17 | endfunction 18 | let s:_s = '' . s:_SID() . '_' 19 | function! s:_function(fstr) abort 20 | return function(substitute(a:fstr, 's:', s:_s, 'g')) 21 | endfunction 22 | endif 23 | 24 | function! vital#{s:plugin_name}#new() abort 25 | return s:new(s:plugin_name) 26 | endfunction 27 | 28 | function! vital#{s:plugin_name}#import(...) abort 29 | if !exists('s:V') 30 | let s:V = s:new(s:plugin_name) 31 | endif 32 | return call(s:V.import, a:000, s:V) 33 | endfunction 34 | 35 | let s:Vital = {} 36 | 37 | function! s:new(plugin_name) abort 38 | let base = deepcopy(s:Vital) 39 | let base._plugin_name = a:plugin_name 40 | return base 41 | endfunction 42 | 43 | function! s:vital_files() abort 44 | if !exists('s:vital_files') 45 | let s:vital_files = map( 46 | \ s:is_vital_vim ? s:_global_vital_files() : s:_self_vital_files(), 47 | \ 'fnamemodify(v:val, ":p:gs?[\\\\/]?/?")') 48 | endif 49 | return copy(s:vital_files) 50 | endfunction 51 | let s:Vital.vital_files = s:_function('s:vital_files') 52 | 53 | function! s:import(name, ...) abort dict 54 | let target = {} 55 | let functions = [] 56 | for a in a:000 57 | if type(a) == type({}) 58 | let target = a 59 | elseif type(a) == type([]) 60 | let functions = a 61 | endif 62 | unlet a 63 | endfor 64 | let module = self._import(a:name) 65 | if empty(functions) 66 | call extend(target, module, 'keep') 67 | else 68 | for f in functions 69 | if has_key(module, f) && !has_key(target, f) 70 | let target[f] = module[f] 71 | endif 72 | endfor 73 | endif 74 | return target 75 | endfunction 76 | let s:Vital.import = s:_function('s:import') 77 | 78 | function! s:load(...) abort dict 79 | for arg in a:000 80 | let [name; as] = type(arg) == type([]) ? arg[: 1] : [arg, arg] 81 | let target = split(join(as, ''), '\W\+') 82 | let dict = self 83 | let dict_type = type({}) 84 | while !empty(target) 85 | let ns = remove(target, 0) 86 | if !has_key(dict, ns) 87 | let dict[ns] = {} 88 | endif 89 | if type(dict[ns]) == dict_type 90 | let dict = dict[ns] 91 | else 92 | unlet dict 93 | break 94 | endif 95 | endwhile 96 | if exists('dict') 97 | call extend(dict, self._import(name)) 98 | endif 99 | unlet arg 100 | endfor 101 | return self 102 | endfunction 103 | let s:Vital.load = s:_function('s:load') 104 | 105 | function! s:unload() abort dict 106 | let s:loaded = {} 107 | let s:cache_sid = {} 108 | unlet! s:vital_files 109 | endfunction 110 | let s:Vital.unload = s:_function('s:unload') 111 | 112 | function! s:exists(name) abort dict 113 | if a:name !~# '\v^\u\w*%(\.\u\w*)*$' 114 | throw 'vital: Invalid module name: ' . a:name 115 | endif 116 | return s:_module_path(a:name) isnot# '' 117 | endfunction 118 | let s:Vital.exists = s:_function('s:exists') 119 | 120 | function! s:search(pattern) abort dict 121 | let paths = s:_extract_files(a:pattern, self.vital_files()) 122 | let modules = sort(map(paths, 's:_file2module(v:val)')) 123 | return s:_uniq(modules) 124 | endfunction 125 | let s:Vital.search = s:_function('s:search') 126 | 127 | function! s:plugin_name() abort dict 128 | return self._plugin_name 129 | endfunction 130 | let s:Vital.plugin_name = s:_function('s:plugin_name') 131 | 132 | function! s:_self_vital_files() abort 133 | let builtin = printf('%s/__%s__/', s:vital_base_dir, s:plugin_name) 134 | let installed = printf('%s/_%s/', s:vital_base_dir, s:plugin_name) 135 | let base = builtin . ',' . installed 136 | return split(globpath(base, '**/*.vim', 1), "\n") 137 | endfunction 138 | 139 | function! s:_global_vital_files() abort 140 | let pattern = 'autoload/vital/__*__/**/*.vim' 141 | return split(globpath(&runtimepath, pattern, 1), "\n") 142 | endfunction 143 | 144 | function! s:_extract_files(pattern, files) abort 145 | let tr = {'.': '/', '*': '[^/]*', '**': '.*'} 146 | let target = substitute(a:pattern, '\.\|\*\*\?', '\=tr[submatch(0)]', 'g') 147 | let regexp = printf('autoload/vital/[^/]\+/%s.vim$', target) 148 | return filter(a:files, 'v:val =~# regexp') 149 | endfunction 150 | 151 | function! s:_file2module(file) abort 152 | let filename = fnamemodify(a:file, ':p:gs?[\\/]?/?') 153 | let tail = matchstr(filename, 'autoload/vital/_\w\+/\zs.*\ze\.vim$') 154 | return join(split(tail, '[\\/]\+'), '.') 155 | endfunction 156 | 157 | " @param {string} name e.g. Data.List 158 | function! s:_import(name) abort dict 159 | if has_key(s:loaded, a:name) 160 | return copy(s:loaded[a:name]) 161 | endif 162 | let module = self._get_module(a:name) 163 | if has_key(module, '_vital_created') 164 | call module._vital_created(module) 165 | endif 166 | let export_module = filter(copy(module), 'v:key =~# "^\\a"') 167 | " Cache module before calling module.vital_loaded() to avoid cyclic 168 | " dependences but remove the cache if module._vital_loaded() fails. 169 | " let s:loaded[a:name] = export_module 170 | let s:loaded[a:name] = export_module 171 | if has_key(module, '_vital_loaded') 172 | try 173 | call module._vital_loaded(vital#{s:plugin_name}#new()) 174 | catch 175 | unlet s:loaded[a:name] 176 | throw 'vital: fail to call ._vital_loaded(): ' . v:exception 177 | endtry 178 | endif 179 | return copy(s:loaded[a:name]) 180 | endfunction 181 | let s:Vital._import = s:_function('s:_import') 182 | 183 | " s:_get_module() returns module object wihch has all script local functions. 184 | function! s:_get_module(name) abort dict 185 | let funcname = s:_import_func_name(self.plugin_name(), a:name) 186 | if s:_exists_autoload_func_with_source(funcname) 187 | return call(funcname, []) 188 | else 189 | return s:_get_builtin_module(a:name) 190 | endif 191 | endfunction 192 | 193 | function! s:_get_builtin_module(name) abort 194 | return s:sid2sfuncs(s:_module_sid(a:name)) 195 | endfunction 196 | 197 | if s:is_vital_vim 198 | " For vital.vim, we can use s:_get_builtin_module directly 199 | let s:Vital._get_module = s:_function('s:_get_builtin_module') 200 | else 201 | let s:Vital._get_module = s:_function('s:_get_module') 202 | endif 203 | 204 | function! s:_import_func_name(plugin_name, module_name) abort 205 | return printf('vital#_%s#%s#import', a:plugin_name, s:_dot_to_sharp(a:module_name)) 206 | endfunction 207 | 208 | function! s:_module_sid(name) abort 209 | let path = s:_module_path(a:name) 210 | if !filereadable(path) 211 | throw 'vital: module not found: ' . a:name 212 | endif 213 | let vital_dir = s:is_vital_vim ? '__\w\+__' : printf('_\{1,2}%s\%%(__\)\?', s:plugin_name) 214 | let base = join([vital_dir, ''], '[/\\]\+') 215 | let p = base . substitute('' . a:name, '\.', '[/\\\\]\\+', 'g') 216 | let sid = s:_sid(path, p) 217 | if !sid 218 | call s:_source(path) 219 | let sid = s:_sid(path, p) 220 | if !sid 221 | throw printf('vital: cannot get from path: %s', path) 222 | endif 223 | endif 224 | return sid 225 | endfunction 226 | 227 | function! s:_module_path(name) abort 228 | return get(s:_extract_files(a:name, s:vital_files()), 0, '') 229 | endfunction 230 | 231 | function! s:_module_sid_base_dir() abort 232 | return s:is_vital_vim ? &rtp : s:project_root 233 | endfunction 234 | 235 | function! s:_dot_to_sharp(name) abort 236 | return substitute(a:name, '\.', '#', 'g') 237 | endfunction 238 | 239 | " It will sources autoload file if a given func is not already defined. 240 | function! s:_exists_autoload_func_with_source(funcname) abort 241 | if exists('*' . a:funcname) 242 | " Return true if a given func is already defined 243 | return 1 244 | endif 245 | " source a file which may include a given func definition and try again. 246 | let path = 'autoload/' . substitute(substitute(a:funcname, '#[^#]*$', '.vim', ''), '#', '/', 'g') 247 | call s:_runtime(path) 248 | return exists('*' . a:funcname) 249 | endfunction 250 | 251 | function! s:_runtime(path) abort 252 | execute 'runtime' fnameescape(a:path) 253 | endfunction 254 | 255 | function! s:_source(path) abort 256 | execute 'source' fnameescape(a:path) 257 | endfunction 258 | 259 | " @vimlint(EVL102, 1, l:_) 260 | " @vimlint(EVL102, 1, l:__) 261 | function! s:_sid(path, filter_pattern) abort 262 | let unified_path = s:_unify_path(a:path) 263 | if has_key(s:cache_sid, unified_path) 264 | return s:cache_sid[unified_path] 265 | endif 266 | for line in filter(split(s:_redir(':scriptnames'), "\n"), 'v:val =~# a:filter_pattern') 267 | let [_, sid, path; __] = matchlist(line, '^\s*\(\d\+\):\s\+\(.\+\)\s*$') 268 | if s:_unify_path(path) is# unified_path 269 | let s:cache_sid[unified_path] = sid 270 | return s:cache_sid[unified_path] 271 | endif 272 | endfor 273 | return 0 274 | endfunction 275 | 276 | function! s:_redir(cmd) abort 277 | let [save_verbose, save_verbosefile] = [&verbose, &verbosefile] 278 | set verbose=0 verbosefile= 279 | redir => res 280 | silent! execute a:cmd 281 | redir END 282 | let [&verbose, &verbosefile] = [save_verbose, save_verbosefile] 283 | return res 284 | endfunction 285 | 286 | if filereadable(expand(':r') . '.VIM') " is case-insensitive or not 287 | let s:_unify_path_cache = {} 288 | " resolve() is slow, so we cache results. 289 | " Note: On windows, vim can't expand path names from 8.3 formats. 290 | " So if getting full path via and $HOME was set as 8.3 format, 291 | " vital load duplicated scripts. Below's :~ avoid this issue. 292 | function! s:_unify_path(path) abort 293 | if has_key(s:_unify_path_cache, a:path) 294 | return s:_unify_path_cache[a:path] 295 | endif 296 | let value = tolower(fnamemodify(resolve(fnamemodify( 297 | \ a:path, ':p')), ':~:gs?[\\/]?/?')) 298 | let s:_unify_path_cache[a:path] = value 299 | return value 300 | endfunction 301 | else 302 | function! s:_unify_path(path) abort 303 | return resolve(fnamemodify(a:path, ':p:gs?[\\/]?/?')) 304 | endfunction 305 | endif 306 | 307 | " copied and modified from Vim.ScriptLocal 308 | let s:SNR = join(map(range(len("\")), '"[\\x" . printf("%0x", char2nr("\"[v:val])) . "]"'), '') 309 | function! s:sid2sfuncs(sid) abort 310 | let fs = split(s:_redir(printf(':function /^%s%s_', s:SNR, a:sid)), "\n") 311 | let r = {} 312 | let pattern = printf('\m^function\s%d_\zs\w\{-}\ze(', a:sid) 313 | for fname in map(fs, 'matchstr(v:val, pattern)') 314 | let r[fname] = function(s:_sfuncname(a:sid, fname)) 315 | endfor 316 | return r 317 | endfunction 318 | 319 | "" Return funcname of script local functions with SID 320 | function! s:_sfuncname(sid, funcname) abort 321 | return printf('%s_%s', a:sid, a:funcname) 322 | endfunction 323 | 324 | if exists('*uniq') 325 | function! s:_uniq(list) abort 326 | return uniq(a:list) 327 | endfunction 328 | else 329 | function! s:_uniq(list) abort 330 | let i = len(a:list) - 1 331 | while 0 < i 332 | if a:list[i] ==# a:list[i - 1] 333 | call remove(a:list, i) 334 | endif 335 | let i -= 1 336 | endwhile 337 | return a:list 338 | endfunction 339 | endif 340 | -------------------------------------------------------------------------------- /autoload/vital/_powerassert/Data/List.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | if v:version > 703 || v:version == 703 && has('patch1170') 5 | function! vital#_powerassert#Data#List#import() abort 6 | return map({'combinations': '', 'and': '', 'sort_by': '', 'foldr1': '', 'sort': '', 'flatten': '', 'has_index': '', 'find_indices': '', 'any': '', 'unshift': '', 'span': '', 'pop': '', 'binary_search': '', 'uniq_by': '', 'or': '', 'all': '', 'zip': '', 'find_last_index': '', 'find': '', 'partition': '', 'map_accum': '', 'permutations': '', 'break': '', 'max_by': '', 'foldl': '', 'foldr': '', 'find_index': '', 'group_by': '', 'take_while': '', 'conj': '', 'push': '', 'char_range': '', 'cons': '', 'foldl1': '', 'intersect': '', 'concat': '', 'shift': '', 'clear': '', 'has_common_items': '', 'product': '', 'zip_fill': '', 'uniq': '', 'has': '', 'min_by': '', 'with_index': ''}, 'function("s:" . v:key)') 7 | endfunction 8 | else 9 | function! s:_SID() abort 10 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 11 | endfunction 12 | execute join(['function! vital#_powerassert#Data#List#import() abort', printf("return map({'combinations': '', 'and': '', 'sort_by': '', 'foldr1': '', 'sort': '', 'flatten': '', 'has_index': '', 'find_indices': '', 'any': '', 'unshift': '', 'span': '', 'pop': '', 'binary_search': '', 'uniq_by': '', 'or': '', 'all': '', 'zip': '', 'find_last_index': '', 'find': '', 'partition': '', 'map_accum': '', 'permutations': '', 'break': '', 'max_by': '', 'foldl': '', 'foldr': '', 'find_index': '', 'group_by': '', 'take_while': '', 'conj': '', 'push': '', 'char_range': '', 'cons': '', 'foldl1': '', 'intersect': '', 'concat': '', 'shift': '', 'clear': '', 'has_common_items': '', 'product': '', 'zip_fill': '', 'uniq': '', 'has': '', 'min_by': '', 'with_index': ''}, \"function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 13 | delfunction s:_SID 14 | endif 15 | " ___vital___ 16 | " Utilities for list. 17 | 18 | let s:save_cpo = &cpo 19 | set cpo&vim 20 | 21 | function! s:pop(list) abort 22 | return remove(a:list, -1) 23 | endfunction 24 | 25 | function! s:push(list, val) abort 26 | call add(a:list, a:val) 27 | return a:list 28 | endfunction 29 | 30 | function! s:shift(list) abort 31 | return remove(a:list, 0) 32 | endfunction 33 | 34 | function! s:unshift(list, val) abort 35 | return insert(a:list, a:val) 36 | endfunction 37 | 38 | function! s:cons(x, xs) abort 39 | return [a:x] + a:xs 40 | endfunction 41 | 42 | function! s:conj(xs, x) abort 43 | return a:xs + [a:x] 44 | endfunction 45 | 46 | " Removes duplicates from a list. 47 | function! s:uniq(list) abort 48 | return s:uniq_by(a:list, 'v:val') 49 | endfunction 50 | 51 | " Removes duplicates from a list. 52 | function! s:uniq_by(list, f) abort 53 | let list = map(copy(a:list), printf('[v:val, %s]', a:f)) 54 | let i = 0 55 | let seen = {} 56 | while i < len(list) 57 | let key = string(list[i][1]) 58 | if has_key(seen, key) 59 | call remove(list, i) 60 | else 61 | let seen[key] = 1 62 | let i += 1 63 | endif 64 | endwhile 65 | return map(list, 'v:val[0]') 66 | endfunction 67 | 68 | function! s:clear(list) abort 69 | if !empty(a:list) 70 | unlet! a:list[0 : len(a:list) - 1] 71 | endif 72 | return a:list 73 | endfunction 74 | 75 | " Concatenates a list of lists. 76 | " XXX: Should we verify the input? 77 | function! s:concat(list) abort 78 | let memo = [] 79 | for Value in a:list 80 | let memo += Value 81 | endfor 82 | return memo 83 | endfunction 84 | 85 | " Take each elements from lists to a new list. 86 | function! s:flatten(list, ...) abort 87 | let limit = a:0 > 0 ? a:1 : -1 88 | let memo = [] 89 | if limit == 0 90 | return a:list 91 | endif 92 | let limit -= 1 93 | for Value in a:list 94 | let memo += 95 | \ type(Value) == type([]) ? 96 | \ s:flatten(Value, limit) : 97 | \ [Value] 98 | unlet! Value 99 | endfor 100 | return memo 101 | endfunction 102 | 103 | " Sorts a list with expression to compare each two values. 104 | " a:a and a:b can be used in {expr}. 105 | function! s:sort(list, expr) abort 106 | if type(a:expr) == type(function('function')) 107 | return sort(a:list, a:expr) 108 | endif 109 | let s:expr = a:expr 110 | return sort(a:list, 's:_compare') 111 | endfunction 112 | 113 | function! s:_compare(a, b) abort 114 | return eval(s:expr) 115 | endfunction 116 | 117 | " Sorts a list using a set of keys generated by mapping the values in the list 118 | " through the given expr. 119 | " v:val is used in {expr} 120 | function! s:sort_by(list, expr) abort 121 | let pairs = map(a:list, printf('[v:val, %s]', a:expr)) 122 | return map(s:sort(pairs, 123 | \ 'a:a[1] ==# a:b[1] ? 0 : a:a[1] ># a:b[1] ? 1 : -1'), 'v:val[0]') 124 | endfunction 125 | 126 | " Returns a maximum value in {list} through given {expr}. 127 | " Returns 0 if {list} is empty. 128 | " v:val is used in {expr} 129 | function! s:max_by(list, expr) abort 130 | if empty(a:list) 131 | return 0 132 | endif 133 | let list = map(copy(a:list), a:expr) 134 | return a:list[index(list, max(list))] 135 | endfunction 136 | 137 | " Returns a minimum value in {list} through given {expr}. 138 | " Returns 0 if {list} is empty. 139 | " v:val is used in {expr} 140 | " FIXME: -0x80000000 == 0x80000000 141 | function! s:min_by(list, expr) abort 142 | return s:max_by(a:list, '-(' . a:expr . ')') 143 | endfunction 144 | 145 | " Returns List of character sequence between [a:from, a:to] 146 | " e.g.: s:char_range('a', 'c') returns ['a', 'b', 'c'] 147 | function! s:char_range(from, to) abort 148 | return map( 149 | \ range(char2nr(a:from), char2nr(a:to)), 150 | \ 'nr2char(v:val)' 151 | \) 152 | endfunction 153 | 154 | " Returns true if a:list has a:value. 155 | " Returns false otherwise. 156 | function! s:has(list, value) abort 157 | return index(a:list, a:value) isnot -1 158 | endfunction 159 | 160 | " Returns true if a:list[a:index] exists. 161 | " Returns false otherwise. 162 | " NOTE: Returns false when a:index is negative number. 163 | function! s:has_index(list, index) abort 164 | " Return true when negative index? 165 | " let index = a:index >= 0 ? a:index : len(a:list) + a:index 166 | return 0 <= a:index && a:index < len(a:list) 167 | endfunction 168 | 169 | " similar to Haskell's Data.List.span 170 | function! s:span(f, xs) abort 171 | let border = len(a:xs) 172 | for i in range(len(a:xs)) 173 | if !eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g')) 174 | let border = i 175 | break 176 | endif 177 | endfor 178 | return border == 0 ? [[], copy(a:xs)] : [a:xs[: border - 1], a:xs[border :]] 179 | endfunction 180 | 181 | " similar to Haskell's Data.List.break 182 | function! s:break(f, xs) abort 183 | return s:span(printf('!(%s)', a:f), a:xs) 184 | endfunction 185 | 186 | " similar to Haskell's Data.List.takeWhile 187 | function! s:take_while(f, xs) abort 188 | return s:span(a:f, a:xs)[0] 189 | endfunction 190 | 191 | " similar to Haskell's Data.List.partition 192 | function! s:partition(f, xs) abort 193 | return [filter(copy(a:xs), a:f), filter(copy(a:xs), '!(' . a:f . ')')] 194 | endfunction 195 | 196 | " similar to Haskell's Prelude.all 197 | function! s:all(f, xs) abort 198 | return !s:any(printf('!(%s)', a:f), a:xs) 199 | endfunction 200 | 201 | " similar to Haskell's Prelude.any 202 | function! s:any(f, xs) abort 203 | return !empty(filter(map(copy(a:xs), a:f), 'v:val')) 204 | endfunction 205 | 206 | " similar to Haskell's Prelude.and 207 | function! s:and(xs) abort 208 | return s:all('v:val', a:xs) 209 | endfunction 210 | 211 | " similar to Haskell's Prelude.or 212 | function! s:or(xs) abort 213 | return s:any('v:val', a:xs) 214 | endfunction 215 | 216 | function! s:map_accum(expr, xs, init) abort 217 | let memo = [] 218 | let init = a:init 219 | for x in a:xs 220 | let expr = substitute(a:expr, 'v:memo', init, 'g') 221 | let expr = substitute(expr, 'v:val', x, 'g') 222 | let [tmp, init] = eval(expr) 223 | call add(memo, tmp) 224 | endfor 225 | return memo 226 | endfunction 227 | 228 | " similar to Haskell's Prelude.foldl 229 | function! s:foldl(f, init, xs) abort 230 | let memo = a:init 231 | for x in a:xs 232 | let expr = substitute(a:f, 'v:val', string(x), 'g') 233 | let expr = substitute(expr, 'v:memo', string(memo), 'g') 234 | unlet memo 235 | let memo = eval(expr) 236 | endfor 237 | return memo 238 | endfunction 239 | 240 | " similar to Haskell's Prelude.foldl1 241 | function! s:foldl1(f, xs) abort 242 | if len(a:xs) == 0 243 | throw 'vital: Data.List: foldl1' 244 | endif 245 | return s:foldl(a:f, a:xs[0], a:xs[1:]) 246 | endfunction 247 | 248 | " similar to Haskell's Prelude.foldr 249 | function! s:foldr(f, init, xs) abort 250 | return s:foldl(a:f, a:init, reverse(copy(a:xs))) 251 | endfunction 252 | 253 | " similar to Haskell's Prelude.fold11 254 | function! s:foldr1(f, xs) abort 255 | if len(a:xs) == 0 256 | throw 'vital: Data.List: foldr1' 257 | endif 258 | return s:foldr(a:f, a:xs[-1], a:xs[0:-2]) 259 | endfunction 260 | 261 | " similar to python's zip() 262 | function! s:zip(...) abort 263 | return map(range(min(map(copy(a:000), 'len(v:val)'))), "map(copy(a:000), 'v:val['.v:val.']')") 264 | endfunction 265 | 266 | " similar to zip(), but goes until the longer one. 267 | function! s:zip_fill(xs, ys, filler) abort 268 | if empty(a:xs) && empty(a:ys) 269 | return [] 270 | elseif empty(a:ys) 271 | return s:cons([a:xs[0], a:filler], s:zip_fill(a:xs[1 :], [], a:filler)) 272 | elseif empty(a:xs) 273 | return s:cons([a:filler, a:ys[0]], s:zip_fill([], a:ys[1 :], a:filler)) 274 | else 275 | return s:cons([a:xs[0], a:ys[0]], s:zip_fill(a:xs[1 :], a:ys[1: ], a:filler)) 276 | endif 277 | endfunction 278 | 279 | " Inspired by Ruby's with_index method. 280 | function! s:with_index(list, ...) abort 281 | let base = a:0 > 0 ? a:1 : 0 282 | return map(copy(a:list), '[v:val, v:key + base]') 283 | endfunction 284 | 285 | " similar to Ruby's detect or Haskell's find. 286 | function! s:find(list, default, f) abort 287 | for x in a:list 288 | if eval(substitute(a:f, 'v:val', string(x), 'g')) 289 | return x 290 | endif 291 | endfor 292 | return a:default 293 | endfunction 294 | 295 | " Returns the index of the first element which satisfies the given expr. 296 | function! s:find_index(xs, f, ...) abort 297 | let len = len(a:xs) 298 | let start = a:0 > 0 ? (a:1 < 0 ? len + a:1 : a:1) : 0 299 | let default = a:0 > 1 ? a:2 : -1 300 | if start >=# len || start < 0 301 | return default 302 | endif 303 | for i in range(start, len - 1) 304 | if eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g')) 305 | return i 306 | endif 307 | endfor 308 | return default 309 | endfunction 310 | 311 | " Returns the index of the last element which satisfies the given expr. 312 | function! s:find_last_index(xs, f, ...) abort 313 | let len = len(a:xs) 314 | let start = a:0 > 0 ? (a:1 < 0 ? len + a:1 : a:1) : len - 1 315 | let default = a:0 > 1 ? a:2 : -1 316 | if start >=# len || start < 0 317 | return default 318 | endif 319 | for i in range(start, 0, -1) 320 | if eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g')) 321 | return i 322 | endif 323 | endfor 324 | return default 325 | endfunction 326 | 327 | " Similar to find_index but returns the list of indices satisfying the given expr. 328 | function! s:find_indices(xs, f, ...) abort 329 | let len = len(a:xs) 330 | let start = a:0 > 0 ? (a:1 < 0 ? len + a:1 : a:1) : 0 331 | let result = [] 332 | if start >=# len || start < 0 333 | return result 334 | endif 335 | for i in range(start, len - 1) 336 | if eval(substitute(a:f, 'v:val', string(a:xs[i]), 'g')) 337 | call add(result, i) 338 | endif 339 | endfor 340 | return result 341 | endfunction 342 | 343 | " Return non-zero if a:list1 and a:list2 have any common item(s). 344 | " Return zero otherwise. 345 | function! s:has_common_items(list1, list2) abort 346 | return !empty(filter(copy(a:list1), 'index(a:list2, v:val) isnot -1')) 347 | endfunction 348 | 349 | function! s:intersect(list1, list2) abort 350 | let items = [] 351 | " for funcref 352 | for X in a:list1 353 | if index(a:list2, X) != -1 && index(items, X) == -1 354 | let items += [X] 355 | endif 356 | endfor 357 | return items 358 | endfunction 359 | 360 | " similar to Ruby's group_by. 361 | function! s:group_by(xs, f) abort 362 | let result = {} 363 | let list = map(copy(a:xs), printf('[v:val, %s]', a:f)) 364 | for x in list 365 | let Val = x[0] 366 | let key = type(x[1]) !=# type('') ? string(x[1]) : x[1] 367 | if has_key(result, key) 368 | call add(result[key], Val) 369 | else 370 | let result[key] = [Val] 371 | endif 372 | unlet Val 373 | endfor 374 | return result 375 | endfunction 376 | 377 | function! s:_default_compare(a, b) abort 378 | return a:a <# a:b ? -1 : a:a ># a:b ? 1 : 0 379 | endfunction 380 | 381 | function! s:binary_search(list, value, ...) abort 382 | let Predicate = a:0 >= 1 ? a:1 : 's:_default_compare' 383 | let dic = a:0 >= 2 ? a:2 : {} 384 | let start = 0 385 | let end = len(a:list) - 1 386 | 387 | while 1 388 | if start > end 389 | return -1 390 | endif 391 | 392 | let middle = (start + end) / 2 393 | 394 | let compared = call(Predicate, [a:value, a:list[middle]], dic) 395 | 396 | if compared < 0 397 | let end = middle - 1 398 | elseif compared > 0 399 | let start = middle + 1 400 | else 401 | return middle 402 | endif 403 | endwhile 404 | endfunction 405 | 406 | function! s:product(lists) abort 407 | let result = [[]] 408 | for pool in a:lists 409 | let tmp = [] 410 | for x in result 411 | let tmp += map(copy(pool), 'x + [v:val]') 412 | endfor 413 | let result = tmp 414 | endfor 415 | return result 416 | endfunction 417 | 418 | function! s:permutations(list, ...) abort 419 | if a:0 > 1 420 | throw 'vital: Data.List: too many arguments' 421 | endif 422 | let r = a:0 == 1 ? a:1 : len(a:list) 423 | if r > len(a:list) 424 | return [] 425 | elseif r < 0 426 | throw 'vital: Data.List: {r} must be non-negative integer' 427 | endif 428 | let n = len(a:list) 429 | let result = [] 430 | for indices in s:product(map(range(r), 'range(n)')) 431 | if len(s:uniq(indices)) == r 432 | call add(result, map(indices, 'a:list[v:val]')) 433 | endif 434 | endfor 435 | return result 436 | endfunction 437 | 438 | function! s:combinations(list, r) abort 439 | if a:r > len(a:list) 440 | return [] 441 | elseif a:r < 0 442 | throw 'vital: Data:List: {r} must be non-negative integer' 443 | endif 444 | let n = len(a:list) 445 | let result = [] 446 | for indices in s:permutations(range(n), a:r) 447 | if s:sort(copy(indices), 'a:a - a:b') == indices 448 | call add(result, map(indices, 'a:list[v:val]')) 449 | endif 450 | endfor 451 | return result 452 | endfunction 453 | 454 | let &cpo = s:save_cpo 455 | unlet s:save_cpo 456 | 457 | " vim:set et ts=2 sts=2 sw=2 tw=0: 458 | -------------------------------------------------------------------------------- /autoload/vital/_powerassert/Vim/VimlCompiler.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | if v:version > 703 || v:version == 703 && has('patch1170') 5 | function! vital#_powerassert#Vim#VimlCompiler#import() abort 6 | return map({'_vital_depends': '', 'import': '', '_vital_loaded': ''}, 'function("s:" . v:key)') 7 | endfunction 8 | else 9 | function! s:_SID() abort 10 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 11 | endfunction 12 | execute join(['function! vital#_powerassert#Vim#VimlCompiler#import() abort', printf("return map({'_vital_depends': '', 'import': '', '_vital_loaded': ''}, \"function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 13 | delfunction s:_SID 14 | endif 15 | " ___vital___ 16 | "============================================================================= 17 | " FILE: autoload/vital/__powerassert__/VimlCompiler.vim 18 | " AUTHOR: haya14busa 19 | " License: MIT license 20 | "============================================================================= 21 | scriptencoding utf-8 22 | let s:save_cpo = &cpo 23 | set cpo&vim 24 | 25 | function! s:_vital_loaded(V) abort 26 | let s:V = a:V 27 | let s:VLP = s:V.import('Vim.VimlParser').import() 28 | endfunction 29 | 30 | function! s:_vital_depends() abort 31 | return ['Vim.VimlParser'] 32 | endfunction 33 | 34 | function! s:import() abort 35 | call extend(s:, s:VLP) 36 | return extend(deepcopy(s:VLP.Compiler), s:VimlCompiler) 37 | endfunction 38 | 39 | let s:VimlCompiler = {} 40 | 41 | function! s:VimlCompiler.compile(node) abort 42 | if a:node.type == s:NODE_TOPLEVEL 43 | return self.compile_toplevel(a:node) 44 | " elseif a:node.type == s:NODE_COMMENT 45 | " return self.compile_comment(a:node) 46 | " elseif a:node.type == s:NODE_EXCMD 47 | " return self.compile_excmd(a:node) 48 | " elseif a:node.type == s:NODE_FUNCTION 49 | " return self.compile_function(a:node) 50 | " elseif a:node.type == s:NODE_DELFUNCTION 51 | " return self.compile_delfunction(a:node) 52 | " elseif a:node.type == s:NODE_RETURN 53 | " return self.compile_return(a:node) 54 | " elseif a:node.type == s:NODE_EXCALL 55 | " return self.compile_excall(a:node) 56 | " elseif a:node.type == s:NODE_LET 57 | " return self.compile_let(a:node) 58 | " elseif a:node.type == s:NODE_UNLET 59 | " return self.compile_unlet(a:node) 60 | " elseif a:node.type == s:NODE_LOCKVAR 61 | " return self.compile_lockvar(a:node) 62 | " elseif a:node.type == s:NODE_UNLOCKVAR 63 | " return self.compile_unlockvar(a:node) 64 | " elseif a:node.type == s:NODE_IF 65 | " return self.compile_if(a:node) 66 | " elseif a:node.type == s:NODE_WHILE 67 | " return self.compile_while(a:node) 68 | " elseif a:node.type == s:NODE_FOR 69 | " return self.compile_for(a:node) 70 | " elseif a:node.type == s:NODE_CONTINUE 71 | " return self.compile_continue(a:node) 72 | " elseif a:node.type == s:NODE_BREAK 73 | " return self.compile_break(a:node) 74 | " elseif a:node.type == s:NODE_TRY 75 | " return self.compile_try(a:node) 76 | " elseif a:node.type == s:NODE_THROW 77 | " return self.compile_throw(a:node) 78 | " elseif a:node.type == s:NODE_ECHO 79 | " return self.compile_echo(a:node) 80 | " elseif a:node.type == s:NODE_ECHON 81 | " return self.compile_echon(a:node) 82 | " elseif a:node.type == s:NODE_ECHOHL 83 | " return self.compile_echohl(a:node) 84 | " elseif a:node.type == s:NODE_ECHOMSG 85 | " return self.compile_echomsg(a:node) 86 | " elseif a:node.type == s:NODE_ECHOERR 87 | " return self.compile_echoerr(a:node) 88 | " elseif a:node.type == s:NODE_EXECUTE 89 | " return self.compile_execute(a:node) 90 | elseif a:node.type == s:NODE_TERNARY 91 | return self.compile_ternary(a:node) 92 | elseif a:node.type == s:NODE_OR 93 | return self.compile_or(a:node) 94 | elseif a:node.type == s:NODE_AND 95 | return self.compile_and(a:node) 96 | elseif a:node.type == s:NODE_EQUAL 97 | return self.compile_equal(a:node) 98 | elseif a:node.type == s:NODE_EQUALCI 99 | return self.compile_equalci(a:node) 100 | elseif a:node.type == s:NODE_EQUALCS 101 | return self.compile_equalcs(a:node) 102 | elseif a:node.type == s:NODE_NEQUAL 103 | return self.compile_nequal(a:node) 104 | elseif a:node.type == s:NODE_NEQUALCI 105 | return self.compile_nequalci(a:node) 106 | elseif a:node.type == s:NODE_NEQUALCS 107 | return self.compile_nequalcs(a:node) 108 | elseif a:node.type == s:NODE_GREATER 109 | return self.compile_greater(a:node) 110 | elseif a:node.type == s:NODE_GREATERCI 111 | return self.compile_greaterci(a:node) 112 | elseif a:node.type == s:NODE_GREATERCS 113 | return self.compile_greatercs(a:node) 114 | elseif a:node.type == s:NODE_GEQUAL 115 | return self.compile_gequal(a:node) 116 | elseif a:node.type == s:NODE_GEQUALCI 117 | return self.compile_gequalci(a:node) 118 | elseif a:node.type == s:NODE_GEQUALCS 119 | return self.compile_gequalcs(a:node) 120 | elseif a:node.type == s:NODE_SMALLER 121 | return self.compile_smaller(a:node) 122 | elseif a:node.type == s:NODE_SMALLERCI 123 | return self.compile_smallerci(a:node) 124 | elseif a:node.type == s:NODE_SMALLERCS 125 | return self.compile_smallercs(a:node) 126 | elseif a:node.type == s:NODE_SEQUAL 127 | return self.compile_sequal(a:node) 128 | elseif a:node.type == s:NODE_SEQUALCI 129 | return self.compile_sequalci(a:node) 130 | elseif a:node.type == s:NODE_SEQUALCS 131 | return self.compile_sequalcs(a:node) 132 | elseif a:node.type == s:NODE_MATCH 133 | return self.compile_match(a:node) 134 | elseif a:node.type == s:NODE_MATCHCI 135 | return self.compile_matchci(a:node) 136 | elseif a:node.type == s:NODE_MATCHCS 137 | return self.compile_matchcs(a:node) 138 | elseif a:node.type == s:NODE_NOMATCH 139 | return self.compile_nomatch(a:node) 140 | elseif a:node.type == s:NODE_NOMATCHCI 141 | return self.compile_nomatchci(a:node) 142 | elseif a:node.type == s:NODE_NOMATCHCS 143 | return self.compile_nomatchcs(a:node) 144 | elseif a:node.type == s:NODE_IS 145 | return self.compile_is(a:node) 146 | elseif a:node.type == s:NODE_ISCI 147 | return self.compile_isci(a:node) 148 | elseif a:node.type == s:NODE_ISCS 149 | return self.compile_iscs(a:node) 150 | elseif a:node.type == s:NODE_ISNOT 151 | return self.compile_isnot(a:node) 152 | elseif a:node.type == s:NODE_ISNOTCI 153 | return self.compile_isnotci(a:node) 154 | elseif a:node.type == s:NODE_ISNOTCS 155 | return self.compile_isnotcs(a:node) 156 | elseif a:node.type == s:NODE_ADD 157 | return self.compile_add(a:node) 158 | elseif a:node.type == s:NODE_SUBTRACT 159 | return self.compile_subtract(a:node) 160 | elseif a:node.type == s:NODE_CONCAT 161 | return self.compile_concat(a:node) 162 | elseif a:node.type == s:NODE_MULTIPLY 163 | return self.compile_multiply(a:node) 164 | elseif a:node.type == s:NODE_DIVIDE 165 | return self.compile_divide(a:node) 166 | elseif a:node.type == s:NODE_REMAINDER 167 | return self.compile_remainder(a:node) 168 | elseif a:node.type == s:NODE_NOT 169 | return self.compile_not(a:node) 170 | elseif a:node.type == s:NODE_PLUS 171 | return self.compile_plus(a:node) 172 | elseif a:node.type == s:NODE_MINUS 173 | return self.compile_minus(a:node) 174 | elseif a:node.type == s:NODE_SUBSCRIPT 175 | return self.compile_subscript(a:node) 176 | elseif a:node.type == s:NODE_SLICE 177 | return self.compile_slice(a:node) 178 | elseif a:node.type == s:NODE_DOT 179 | return self.compile_dot(a:node) 180 | elseif a:node.type == s:NODE_CALL 181 | return self.compile_call(a:node) 182 | elseif a:node.type == s:NODE_NUMBER 183 | return self.compile_number(a:node) 184 | elseif a:node.type == s:NODE_STRING 185 | return self.compile_string(a:node) 186 | elseif a:node.type == s:NODE_LIST 187 | return self.compile_list(a:node) 188 | elseif a:node.type == s:NODE_DICT 189 | return self.compile_dict(a:node) 190 | elseif a:node.type == s:NODE_OPTION 191 | return self.compile_option(a:node) 192 | elseif a:node.type == s:NODE_IDENTIFIER 193 | return self.compile_identifier(a:node) 194 | elseif a:node.type == s:NODE_CURLYNAME 195 | return self.compile_curlyname(a:node) 196 | elseif a:node.type == s:NODE_ENV 197 | return self.compile_env(a:node) 198 | elseif a:node.type == s:NODE_REG 199 | return self.compile_reg(a:node) 200 | elseif a:node.type == s:NODE_CURLYNAMEPART 201 | return self.compile_curlynamepart(a:node) 202 | elseif a:node.type == s:NODE_CURLYNAMEEXPR 203 | return self.compile_curlynameexpr(a:node) 204 | else 205 | throw printf('VimlCompiler: unknown node: %s', string(a:node)) 206 | endif 207 | endfunction 208 | 209 | function! s:VimlCompiler.compile_ternary(node) abort 210 | return printf('(%s ? %s : %s)', self.compile(a:node.cond), self.compile(a:node.left), self.compile(a:node.right)) 211 | endfunction 212 | 213 | function! s:VimlCompiler.compile_or(node) 214 | return printf('(%s || %s)', self.compile(a:node.left), self.compile(a:node.right)) 215 | endfunction 216 | 217 | function! s:VimlCompiler.compile_and(node) 218 | return printf('(%s && %s)', self.compile(a:node.left), self.compile(a:node.right)) 219 | endfunction 220 | 221 | function! s:VimlCompiler.compile_equal(node) 222 | return printf('(%s == %s)', self.compile(a:node.left), self.compile(a:node.right)) 223 | endfunction 224 | 225 | function! s:VimlCompiler.compile_equalci(node) 226 | return printf('(%s ==? %s)', self.compile(a:node.left), self.compile(a:node.right)) 227 | endfunction 228 | 229 | function! s:VimlCompiler.compile_equalcs(node) 230 | return printf('(%s ==# %s)', self.compile(a:node.left), self.compile(a:node.right)) 231 | endfunction 232 | 233 | function! s:VimlCompiler.compile_nequal(node) 234 | return printf('(%s != %s)', self.compile(a:node.left), self.compile(a:node.right)) 235 | endfunction 236 | 237 | function! s:VimlCompiler.compile_nequalci(node) 238 | return printf('(%s !=? %s)', self.compile(a:node.left), self.compile(a:node.right)) 239 | endfunction 240 | 241 | function! s:VimlCompiler.compile_nequalcs(node) 242 | return printf('(%s !=# %s)', self.compile(a:node.left), self.compile(a:node.right)) 243 | endfunction 244 | 245 | function! s:VimlCompiler.compile_greater(node) 246 | return printf('(%s > %s)', self.compile(a:node.left), self.compile(a:node.right)) 247 | endfunction 248 | 249 | function! s:VimlCompiler.compile_greaterci(node) 250 | return printf('(%s >? %s)', self.compile(a:node.left), self.compile(a:node.right)) 251 | endfunction 252 | 253 | function! s:VimlCompiler.compile_greatercs(node) 254 | return printf('(%s ># %s)', self.compile(a:node.left), self.compile(a:node.right)) 255 | endfunction 256 | 257 | function! s:VimlCompiler.compile_gequal(node) 258 | return printf('(%s >= %s)', self.compile(a:node.left), self.compile(a:node.right)) 259 | endfunction 260 | 261 | function! s:VimlCompiler.compile_gequalci(node) 262 | return printf('(%s >=? %s)', self.compile(a:node.left), self.compile(a:node.right)) 263 | endfunction 264 | 265 | function! s:VimlCompiler.compile_gequalcs(node) 266 | return printf('(%s >=# %s)', self.compile(a:node.left), self.compile(a:node.right)) 267 | endfunction 268 | 269 | function! s:VimlCompiler.compile_smaller(node) 270 | return printf('(%s < %s)', self.compile(a:node.left), self.compile(a:node.right)) 271 | endfunction 272 | 273 | function! s:VimlCompiler.compile_smallerci(node) 274 | return printf('(%s )\"", a:cmdname, s:_funcname('s:assert')) 40 | return 'execute ' . cmd 41 | endfunction 42 | 43 | " RETURN: command to execute (s:_assert()) to evaluate given expression 44 | " in the same scope with caller's one 45 | " @param {string} expr_str expr_str may contain additional message for defined 46 | " command like `:PowerAssert 1 == 2, 'additional msg'` 47 | " @param {string?} additional_msg? additional_msg argument for calling this 48 | " func directly. 49 | function! s:assert(expr_str, ...) abort 50 | if s:_config().__debug__ 51 | let additional_msg = get(a:, 1, '') 52 | " assert !empty(empty_str) 53 | let _assert = s:_funcname('s:_assert') 54 | let args = printf('%s, %s, %s', string(a:expr_str), a:expr_str, string(additional_msg)) 55 | let rhs = escape(printf('%s(%s)', _assert, args), '"\') 56 | return 'execute "execute" "' . rhs . '"' 57 | else 58 | return '' 59 | endif 60 | endfunction 61 | 62 | " RETURN: throw command which display graphical assertion result if bool is 63 | " falsy 64 | function! s:_assert(argstr, bool, ...) abort 65 | " assert !empty(empty_str) 66 | if ! a:bool 67 | let message = get(a:, 1, '') 68 | let expr_str = a:argstr 69 | if message !=# '' 70 | " expr_str(a:argstr) may contain message even though message is given as an 71 | " argument if this func(including a:argstr) is used for defined 72 | " assertion :command. 73 | let [expr_str, _] = s:_parse_cmd_trail_msg(expr_str) 74 | endif 75 | " Aggregate nodes to evaluate which we want to inspect and eval in the 76 | " same scope with caller's one by returnign comamnd with nodes to eval as 77 | " arguments. 78 | let nodes = s:_aggregate_node_strs_to_eval(expr_str) 79 | let args = printf('%s, [%s], %s', string(expr_str), join(nodes, ', '), string(message)) 80 | let rhs = escape(printf('%s(%s)', s:_funcname('s:_throw_cmd'), args), '"\') 81 | return 'execute "execute" "' . rhs . '"' 82 | else 83 | return '' 84 | endif 85 | endfunction 86 | 87 | " RETURN: generate pseudo-throw command with graphical assertion result 88 | " from evaluated_nodes which evaluated in the same scope with caller's one 89 | function! s:_throw_cmd(whole_expr, evaluated_nodes, message) abort 90 | let head_msg = s:String.trim_end(printf('vital: PowerAssert: %s', a:message)) 91 | let msgs = [head_msg] + s:_build_assertion_graph(a:whole_expr, a:evaluated_nodes) 92 | if s:_config().__pseudo_throw__ 93 | return s:_pseudo_throw_cmd(join(msgs, "\n")) 94 | else 95 | return s:_build_actual_throw_cmd(join(msgs, "\n")) 96 | endif 97 | endfunction 98 | 99 | " Vim cannot output multiple lines with `:echom` nor `:throw`, so execute 100 | " `:echom` each line and execute `:throw` additionally just for aborting 101 | function! s:_pseudo_throw_cmd(msg, ...) abort 102 | let do_throw = get(a:, 1, 1) 103 | return join([ 104 | \ 'try', 105 | \ ' throw ' . string(a:msg), 106 | \ 'catch', 107 | \ ' echohl ErrorMsg', 108 | \ ' echom v:throwpoint', 109 | \ ' for s:__vital_assert_line in split(v:exception, "\n")', 110 | \ ' echom s:__vital_assert_line', 111 | \ ' endfor', 112 | \ ' unlet s:__vital_assert_line', 113 | \ ' echohl None', 114 | \ 'endtry' 115 | \ ] + (do_throw ? ['throw "vital: PowerAssert: abort"'] : []), '|') 116 | endfunction 117 | 118 | function! s:_build_actual_throw_cmd(msg) abort 119 | return printf('throw "%s"', escape(a:msg, '"\')) 120 | endfunction 121 | 122 | " @evaluated_nodes List[{'col': Number, 'expr': Expr}] 123 | function! s:_build_assertion_graph(whole_expr, evaluated_nodes) abort 124 | let lines = [a:whole_expr] 125 | " Add str_value to Node 126 | let nodes = map(copy(a:evaluated_nodes), 127 | \ "extend(v:val, {'str_value': s:_to_expr_string(v:val)})") 128 | let reverse_sorted_cols = s:List.sort(map(nodes, 'v:val.pos.col'), 'a:a - a:b') 129 | let reverse_sorted_nodes = reverse(s:List.sort_by(a:evaluated_nodes, 'v:val.pos.col')) 130 | 131 | " 1st line 132 | let lines += [s:_cols_line(reverse_sorted_cols)] 133 | 134 | " build by row for each loop 135 | while !empty(reverse_sorted_nodes) 136 | let rest_nodes = [] 137 | let nodes_for_row = [] 138 | " wall moves to left... for each for loop 139 | let wall = s:INT.MAX 140 | for node in reverse_sorted_nodes 141 | if (node.pos.col - 1) + strdisplaywidth(node.str_value) < wall 142 | let nodes_for_row += [node] 143 | let wall = node.pos.col - 1 144 | else 145 | let rest_nodes += [node] 146 | let wall = node.pos.col 147 | endif 148 | endfor 149 | let lines += [s:_build_line(nodes_for_row, reverse_sorted_cols)] 150 | let reverse_sorted_cols = reverse(map(copy(rest_nodes), 'v:val.pos.col')) 151 | let reverse_sorted_nodes = rest_nodes 152 | endwhile 153 | return lines 154 | endfunction 155 | 156 | " >>> let nodes = [ 157 | " >>> \ {'pos': {'col': 1}, 'str_value': "'xxx'"}, 158 | " >>> \ {'pos': {'col': 10}, 'str_value': "'yyyy'"} 159 | " >>> \ ] 160 | " >>> let sorted_cols = [1, 7, 10, 18] 161 | " >>> echo s:_build_line(nodes, sorted_cols) 162 | " 'xxx' | 'yyyy' | 163 | function! s:_build_line(nodes, sorted_cols) abort 164 | let node_map = {} 165 | for node in a:nodes 166 | let node_map[node.pos.col] = node.str_value 167 | endfor 168 | let line = '' 169 | for col in a:sorted_cols 170 | let str = get(node_map, col, '|') 171 | let line .= repeat(' ', col - strdisplaywidth(line) - 1) . str 172 | endfor 173 | return line 174 | endfunction 175 | 176 | function! s:_to_expr_string(node_with_pos) abort 177 | let max_length = s:_config().__max_length__ 178 | let expr_string = s:SafeString.string(a:node_with_pos.expr) 179 | return max_length < 0 180 | \ ? expr_string 181 | \ : s:String.trim_end(s:String.truncate_skipping(expr_string, max_length, 1, '...')) 182 | endfunction 183 | 184 | " >>> echo s:_cols_line([1, 2, 5, 10]) 185 | " || | | 186 | function! s:_cols_line(cols) abort 187 | let max = max(a:cols) 188 | let strs = map(range(max), "' '") 189 | for col in a:cols 190 | let strs[col - 1] = '|' 191 | endfor 192 | return join(strs, '') 193 | endfunction 194 | 195 | " RETURN: [Node, ...] 196 | " NODE: which could be evaluated 197 | function! s:_flatten_evaluatable_node(expr_node) abort 198 | let stack = [a:expr_node] 199 | let flat_nodes = [] 200 | 201 | while !empty(stack) 202 | let node = s:List.pop(stack) 203 | if node.type isnot# s:VimlParser.NODE_CURLYNAME 204 | call s:List.push(flat_nodes, node) 205 | endif 206 | let stack += s:_next_evaluatable_node(node) 207 | endwhile 208 | 209 | return flat_nodes 210 | endfunction 211 | 212 | " @return List[Node] 213 | function! s:_next_evaluatable_node(node) abort 214 | if a:node.type == s:VimlParser.NODE_TOPLEVEL 215 | " XXX: ??? 216 | return [] 217 | " elseif a:node.type == s:VimlParser.NODE_COMMENT 218 | " return [] 219 | " elseif a:node.type == s:VimlParser.NODE_EXCMD 220 | " return [] 221 | " elseif a:node.type == s:VimlParser.NODE_FUNCTION 222 | " return [] 223 | " elseif a:node.type == s:VimlParser.NODE_DELFUNCTION 224 | " return [] 225 | " elseif a:node.type == s:VimlParser.NODE_RETURN 226 | " return [] 227 | " elseif a:node.type == s:VimlParser.NODE_EXCALL 228 | " return [] 229 | " elseif a:node.type == s:VimlParser.NODE_LET 230 | " return [] 231 | " elseif a:node.type == s:VimlParser.NODE_UNLET 232 | " return [] 233 | " elseif a:node.type == s:VimlParser.NODE_LOCKVAR 234 | " return [] 235 | " elseif a:node.type == s:VimlParser.NODE_UNLOCKVAR 236 | " return [] 237 | " elseif a:node.type == s:VimlParser.NODE_IF 238 | " return [] 239 | " elseif a:node.type == s:VimlParser.NODE_WHILE 240 | " return [] 241 | " elseif a:node.type == s:VimlParser.NODE_FOR 242 | " return [] 243 | " elseif a:node.type == s:VimlParser.NODE_CONTINUE 244 | " return [] 245 | " elseif a:node.type == s:VimlParser.NODE_BREAK 246 | " return [] 247 | " elseif a:node.type == s:VimlParser.NODE_TRY 248 | " return [] 249 | " elseif a:node.type == s:VimlParser.NODE_THROW 250 | " return [] 251 | " elseif a:node.type == s:VimlParser.NODE_ECHO 252 | " return [] 253 | " elseif a:node.type == s:VimlParser.NODE_ECHON 254 | " return [] 255 | " elseif a:node.type == s:VimlParser.NODE_ECHOHL 256 | " return [] 257 | " elseif a:node.type == s:VimlParser.NODE_ECHOMSG 258 | " return [] 259 | " elseif a:node.type == s:VimlParser.NODE_ECHOERR 260 | " return [] 261 | " elseif a:node.type == s:VimlParser.NODE_EXECUTE 262 | " return [] 263 | elseif a:node.type == s:VimlParser.NODE_TERNARY 264 | return [a:node.cond, a:node.left, a:node.right] 265 | elseif a:node.type == s:VimlParser.NODE_OR 266 | return [a:node.left, a:node.right] 267 | elseif a:node.type == s:VimlParser.NODE_AND 268 | return [a:node.left, a:node.right] 269 | elseif a:node.type == s:VimlParser.NODE_EQUAL 270 | return [a:node.left, a:node.right] 271 | elseif a:node.type == s:VimlParser.NODE_EQUALCI 272 | return [a:node.left, a:node.right] 273 | elseif a:node.type == s:VimlParser.NODE_EQUALCS 274 | return [a:node.left, a:node.right] 275 | elseif a:node.type == s:VimlParser.NODE_NEQUAL 276 | return [a:node.left, a:node.right] 277 | elseif a:node.type == s:VimlParser.NODE_NEQUALCI 278 | return [a:node.left, a:node.right] 279 | elseif a:node.type == s:VimlParser.NODE_NEQUALCS 280 | return [a:node.left, a:node.right] 281 | elseif a:node.type == s:VimlParser.NODE_GREATER 282 | return [a:node.left, a:node.right] 283 | elseif a:node.type == s:VimlParser.NODE_GREATERCI 284 | return [a:node.left, a:node.right] 285 | elseif a:node.type == s:VimlParser.NODE_GREATERCS 286 | return [a:node.left, a:node.right] 287 | elseif a:node.type == s:VimlParser.NODE_GEQUAL 288 | return [a:node.left, a:node.right] 289 | elseif a:node.type == s:VimlParser.NODE_GEQUALCI 290 | return [a:node.left, a:node.right] 291 | elseif a:node.type == s:VimlParser.NODE_GEQUALCS 292 | return [a:node.left, a:node.right] 293 | elseif a:node.type == s:VimlParser.NODE_SMALLER 294 | return [a:node.left, a:node.right] 295 | elseif a:node.type == s:VimlParser.NODE_SMALLERCI 296 | return [a:node.left, a:node.right] 297 | elseif a:node.type == s:VimlParser.NODE_SMALLERCS 298 | return [a:node.left, a:node.right] 299 | elseif a:node.type == s:VimlParser.NODE_SEQUAL 300 | return [a:node.left, a:node.right] 301 | elseif a:node.type == s:VimlParser.NODE_SEQUALCI 302 | return [a:node.left, a:node.right] 303 | elseif a:node.type == s:VimlParser.NODE_SEQUALCS 304 | return [a:node.left, a:node.right] 305 | elseif a:node.type == s:VimlParser.NODE_MATCH 306 | return [a:node.left, a:node.right] 307 | elseif a:node.type == s:VimlParser.NODE_MATCHCI 308 | return [a:node.left, a:node.right] 309 | elseif a:node.type == s:VimlParser.NODE_MATCHCS 310 | return [a:node.left, a:node.right] 311 | elseif a:node.type == s:VimlParser.NODE_NOMATCH 312 | return [a:node.left, a:node.right] 313 | elseif a:node.type == s:VimlParser.NODE_NOMATCHCI 314 | return [a:node.left, a:node.right] 315 | elseif a:node.type == s:VimlParser.NODE_NOMATCHCS 316 | return [a:node.left, a:node.right] 317 | elseif a:node.type == s:VimlParser.NODE_IS 318 | return [a:node.left, a:node.right] 319 | elseif a:node.type == s:VimlParser.NODE_ISCI 320 | return [a:node.left, a:node.right] 321 | elseif a:node.type == s:VimlParser.NODE_ISCS 322 | return [a:node.left, a:node.right] 323 | elseif a:node.type == s:VimlParser.NODE_ISNOT 324 | return [a:node.left, a:node.right] 325 | elseif a:node.type == s:VimlParser.NODE_ISNOTCI 326 | return [a:node.left, a:node.right] 327 | elseif a:node.type == s:VimlParser.NODE_ISNOTCS 328 | return [a:node.left, a:node.right] 329 | elseif a:node.type == s:VimlParser.NODE_ADD 330 | return [a:node.left, a:node.right] 331 | elseif a:node.type == s:VimlParser.NODE_SUBTRACT 332 | return [a:node.left, a:node.right] 333 | elseif a:node.type == s:VimlParser.NODE_CONCAT 334 | return [a:node.left, a:node.right] 335 | elseif a:node.type == s:VimlParser.NODE_MULTIPLY 336 | return [a:node.left, a:node.right] 337 | elseif a:node.type == s:VimlParser.NODE_DIVIDE 338 | return [a:node.left, a:node.right] 339 | elseif a:node.type == s:VimlParser.NODE_REMAINDER 340 | return [a:node.left, a:node.right] 341 | elseif a:node.type == s:VimlParser.NODE_NOT 342 | return [a:node.left] 343 | elseif a:node.type == s:VimlParser.NODE_PLUS 344 | return [a:node.left] 345 | elseif a:node.type == s:VimlParser.NODE_MINUS 346 | return [a:node.left] 347 | elseif a:node.type == s:VimlParser.NODE_SUBSCRIPT 348 | return [a:node.left, a:node.right] 349 | elseif a:node.type == s:VimlParser.NODE_SLICE 350 | return [a:node.left] + filter(copy(a:node.rlist), 'type(v:val) is# type({})') 351 | elseif a:node.type == s:VimlParser.NODE_DOT 352 | " Right is just a accessor 353 | return [a:node.left] 354 | elseif a:node.type == s:VimlParser.NODE_CALL 355 | let funcname = ( 356 | \ a:node.left.type == s:VimlParser.NODE_CURLYNAME || 357 | \ a:node.left.type == s:VimlParser.NODE_DOT 358 | \ ) ? [a:node.left] : [] 359 | return funcname + a:node.rlist 360 | elseif a:node.type == s:VimlParser.NODE_NUMBER 361 | return [] 362 | elseif a:node.type == s:VimlParser.NODE_STRING 363 | return [] 364 | elseif a:node.type == s:VimlParser.NODE_LIST 365 | return a:node.value 366 | elseif a:node.type == s:VimlParser.NODE_DICT 367 | return s:List.flatten(a:node.value) 368 | elseif a:node.type == s:VimlParser.NODE_OPTION 369 | return [] 370 | elseif a:node.type == s:VimlParser.NODE_IDENTIFIER 371 | return [] 372 | elseif a:node.type == s:VimlParser.NODE_CURLYNAME 373 | return filter(copy(a:node.value), 'v:val.curly is# 1') 374 | elseif a:node.type == s:VimlParser.NODE_ENV 375 | return [] 376 | elseif a:node.type == s:VimlParser.NODE_REG 377 | return [] 378 | elseif a:node.type == s:VimlParser.NODE_CURLYNAMEPART 379 | return [] 380 | elseif a:node.type == s:VimlParser.NODE_CURLYNAMEEXPR 381 | return type(a:node.value.value) is# type([]) ? [a:node.value] : [] 382 | else 383 | throw printf('PowerAssert: unknown node: %s', string(a:node)) 384 | endif 385 | endfunction 386 | 387 | function! s:_filter_out_primitive_node(nodes) abort 388 | let excludes = [ 389 | \ s:VimlParser.NODE_NUMBER, 390 | \ s:VimlParser.NODE_STRING, 391 | \ s:VimlParser.NODE_LIST, 392 | \ s:VimlParser.NODE_DICT 393 | \ ] 394 | return filter(copy(a:nodes), 'index(excludes, v:val.type) is# -1') 395 | endfunction 396 | 397 | " To eval `expr` and get `pos` and evaluated expression 398 | function! s:_compile_expr_with_pos(node) abort 399 | return {'pos': a:node.pos, 'expr': s:_compile(a:node)} 400 | endfunction 401 | 402 | function! s:_node_to_eval_str(node) abort 403 | return printf("{'pos': %s, 'expr': %s}", string(a:node.pos), a:node.expr) 404 | endfunction 405 | 406 | function! s:_compile(expr_node) abort 407 | let c = s:VimlCompiler.new() 408 | return c.compile(deepcopy(a:expr_node)) 409 | endfunction 410 | 411 | function! s:_parse_expr(expr_str) abort 412 | " assert !empty(empty_str) 413 | let reader = s:VimlParser.StringReader.new(a:expr_str) 414 | let expr_parser = s:VimlParser.ExprParser.new(reader) 415 | return expr_parser.parse() 416 | endfunction 417 | 418 | " RETURN: List[String(NODE)] 419 | function! s:_aggregate_node_strs_to_eval(expr_str) abort 420 | return map(s:_aggregate_nodes_to_eval(a:expr_str), 's:_node_to_eval_str(v:val)') 421 | endfunction 422 | 423 | " RETURN: List[NODE] 424 | function! s:_aggregate_nodes_to_eval(expr_str) abort 425 | " assert !empty(empty_str) 426 | let node = s:_parse_expr(a:expr_str) 427 | let nodes_to_eval = s:_filter_out_primitive_node(s:_flatten_evaluatable_node(node)) 428 | return map(nodes_to_eval, 's:_compile_expr_with_pos(v:val)') 429 | endfunction 430 | 431 | function! s:_SID() abort 432 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 433 | endfunction 434 | 435 | let s:_s = '' . s:_SID() . '_' 436 | 437 | function! s:_funcname(funcname) abort 438 | return substitute(a:funcname, 's:', s:_s, 'g') 439 | endfunction 440 | 441 | " s:_parse_cmd_trail_msg() parses optional trailing message for :command. 442 | " The trailing message should be quoted with single or double quote and 443 | " followed by `,` + optional spaces. e.g. `, 'this is msg'` 444 | " @param {string} arg 'arg for command argument' 445 | " @return ({body}, {message}) 'returns cmd body and additinal msg(default is 446 | " Example: 447 | " echo s:_parse_cmd_trail_msg('x == 1, "this is msg"') 448 | " " {body} -> ^^^^^^ ^^^^^^^^^^^ <- {message} 449 | " echo s:_parse_cmd_trail_msg('x == 1') " {message} is optional 450 | " Output: 451 | " ['x == 1', 'this is msg'] 452 | " ['x == 1', ''] 453 | function! s:_parse_cmd_trail_msg(arg) abort 454 | " ^.* is for avoiding to match middle comma by matching greedy. 455 | " e.g.: has_key({}, 'x'), 'flags' 456 | " ^ <- avoid! 457 | let xs = split(a:arg, '\v^.*\zs,\s*\ze([''"]).{-}\1\s*$') 458 | let body = xs[0] 459 | let quoted_msg = get(xs, 1, '') 460 | " XXX: it doen't handle '' in single quote... 461 | let message = quoted_msg !=# '' ? quoted_msg[1:-2] : quoted_msg 462 | return [body, message] 463 | endfunction 464 | 465 | let &cpo = s:save_cpo 466 | unlet s:save_cpo 467 | " __END__ 468 | " vim: expandtab softtabstop=2 shiftwidth=2 foldmethod=marker 469 | -------------------------------------------------------------------------------- /test/PowerAssert.vimspec: -------------------------------------------------------------------------------- 1 | Describe PowerAssert 2 | 3 | Before all 4 | let V = vital#vital#new() 5 | let g:PowerAssert = V.import('Vim.PowerAssert') 6 | let s:assert = g:PowerAssert.assert 7 | let ScriptLocal = V.import('Vim.ScriptLocal') 8 | let sfuncs = ScriptLocal.sfuncs('autoload/vital/__powerassert__/Vim/PowerAssert.vim') 9 | End 10 | 11 | function! CheckThrow(expr) abort 12 | return printf("Throws /vital: PowerAssert:/ :execute g:PowerAssert.assert('%s')", a:expr) 13 | endfunction 14 | 15 | Describe descriptive message 16 | It should output descriptive message on failure 17 | execute g:PowerAssert.define('PowerAssert') 18 | let msg = escape(join([ 19 | \ "vital: PowerAssert:", 20 | \ "index(x.ary, l:zero) is# s:two", 21 | \ " ||| | | |", 22 | \ " ||| 0 0 2", 23 | \ " ||[1, 2, 3]", 24 | \ " |{'ary': [1, 2, 3], 'power': 'assert'}", 25 | \ " -1" 26 | \ ], "\n"), '[]') 27 | let x = { 'ary': [1, 2, 3], 'power': 'assert' } 28 | let l:zero = 0 29 | let s:two = 2 30 | execute printf('Throws /%s/ :PowerAssert index(x.ary, l:zero) is# s:two', msg) 31 | delcommand PowerAssert 32 | End 33 | 34 | It should support multibyte 35 | execute g:PowerAssert.define('PowerAssert') 36 | let msg = escape(join([ 37 | \ "vital: PowerAssert:", 38 | \ "threeeeee is# (index(x.ary, l:zero) is# s:two)", 39 | \ "| | ||| | | |", 40 | \ "'さん3333' 0 3|| 0 0 2", 41 | \ " |[1, 'にーーー', 3, 0]", 42 | \ " {'ary': [1, 'にーーー', 3, 0], 'power': 'assert'}" 43 | \ ], "\n"), '[]') 44 | let x = { 'ary': [1, 'にーーー', 3, 0], 'power': 'assert' } 45 | let l:zero = 0 46 | let s:two = 2 47 | let threeeeee = 'さん3333' 48 | execute printf('Throws /%s/ :PowerAssert threeeeee is# (index(x.ary, l:zero) is# s:two)', msg) 49 | delcommand PowerAssert 50 | End 51 | End 52 | 53 | Describe additional message 54 | It should output additional message 55 | execute g:PowerAssert.define('PowerAssert') 56 | let msg = escape(join([ 57 | \ "vital: PowerAssert: THIS IS ADDITIONAL MESSAGE", 58 | \ "index(x.ary, l:zero) is# s:two", 59 | \ " ||| | | |", 60 | \ " ||| 0 0 2", 61 | \ " ||[1, 2, 3]", 62 | \ " |{'ary': [1, 2, 3], 'power': 'assert'}", 63 | \ " -1" 64 | \ ], "\n"), '[]') 65 | let x = { 'ary': [1, 2, 3], 'power': 'assert' } 66 | let l:zero = 0 67 | let s:two = 2 68 | execute printf('Throws /%s/ :PowerAssert index(x.ary, l:zero) is# s:two, ''%s''', msg, 'THIS IS ADDITIONAL MESSAGE') 69 | delcommand PowerAssert 70 | End 71 | 72 | It should be parsed and handled correctly 73 | execute g:PowerAssert.define('PowerAssert') 74 | Throws /vital: PowerAssert: hoge foo/ :PowerAssert has_key({}, 'x'), 'hoge foo' 75 | delcommand PowerAssert 76 | End 77 | End 78 | 79 | Describe .assert 80 | It does nothing if given expression is true 81 | execute s:assert('1 == 1') 82 | execute s:assert('1') 83 | End 84 | 85 | It support func call 86 | let xs = [1, 2, 3] 87 | execute s:assert('index(xs, 2) == 1') 88 | End 89 | 90 | It should support expression with double quote 91 | let _save = g:__vital_power_assert_config.__pseudo_throw__ 92 | for bool in [0, 1] 93 | let g:__vital_power_assert_config.__pseudo_throw__ = bool 94 | try 95 | let x = 'vim' 96 | execute s:assert('x != "vi"') 97 | Throws /vital: PowerAssert:/ :execute g:PowerAssert.assert('x == "vi"') 98 | finally 99 | let g:__vital_power_assert_config.__pseudo_throw__ = _save 100 | endtry 101 | endfor 102 | End 103 | 104 | It should support expression with single quote 105 | let _save = g:__vital_power_assert_config.__pseudo_throw__ 106 | for bool in [0, 1] 107 | let g:__vital_power_assert_config.__pseudo_throw__ = bool 108 | try 109 | let x = 'vim' 110 | execute s:assert("x != 'vi'") 111 | Throws /vital: PowerAssert:/ :execute g:PowerAssert.assert("x == 'vi'") 112 | finally 113 | let g:__vital_power_assert_config.__pseudo_throw__ = _save 114 | endtry 115 | endfor 116 | End 117 | 118 | It should support `\` in strings 119 | let _save = g:__vital_power_assert_config.__pseudo_throw__ 120 | for bool in [0, 1] 121 | let g:__vital_power_assert_config.__pseudo_throw__ = bool 122 | try 123 | execute s:assert("'?' != '\\?'") 124 | Throws /vital: PowerAssert:/ :execute g:PowerAssert.assert("'?' == '\\?'") 125 | finally 126 | let g:__vital_power_assert_config.__pseudo_throw__ = _save 127 | endtry 128 | endfor 129 | End 130 | 131 | It should output additional message 132 | let _save = g:__vital_power_assert_config.__pseudo_throw__ 133 | let g:__vital_power_assert_config.__pseudo_throw__ = 0 134 | try 135 | let x = 'vim' 136 | execute s:assert("x != 'vi'") 137 | Throws /vital: PowerAssert: THIS IS ADDITIONAL MESSAGE/ :execute g:PowerAssert.assert("x == 'vi'", 'THIS IS ADDITIONAL MESSAGE') 138 | finally 139 | let g:__vital_power_assert_config.__pseudo_throw__ = _save 140 | endtry 141 | End 142 | 143 | It should support nested dict 144 | let _save = g:__vital_power_assert_config.__pseudo_throw__ 145 | let g:__vital_power_assert_config.__pseudo_throw__ = 0 146 | try 147 | let d = {} 148 | let d.d = d 149 | execute s:assert("d != {}") 150 | Throws /vital: PowerAssert: Expected Error/ :execute g:PowerAssert.assert("d == {}", 'Expected Error') 151 | finally 152 | let g:__vital_power_assert_config.__pseudo_throw__ = _save 153 | endtry 154 | End 155 | 156 | It throws abort exception with falsy expression 157 | Throws /vital: PowerAssert:/ :execute g:PowerAssert.assert('2 == 1') 158 | Throws /vital: PowerAssert:/ :execute g:PowerAssert.assert('0') 159 | End 160 | 161 | End 162 | 163 | Describe ._assert 164 | It returns empty string if bool is true 165 | Assert Equals(sfuncs._assert('vim', 1), '') 166 | End 167 | 168 | It throws abort exception if bool is false 169 | let g:sfuncs = sfuncs 170 | Throws /vital: PowerAssert:/ :execute g:sfuncs._assert('2 == 1', 0) 171 | unlet g:sfuncs 172 | End 173 | End 174 | 175 | Describe ._next_evaluatable_node 176 | It string 177 | let node = sfuncs._parse_expr('"hoge"') 178 | Assert Equals(sfuncs._next_evaluatable_node(node), []) 179 | End 180 | 181 | It number 182 | let node = sfuncs._parse_expr('1') 183 | Assert Equals(sfuncs._next_evaluatable_node(node), []) 184 | End 185 | 186 | It list 187 | let node = sfuncs._parse_expr('[1, 2]') 188 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 2) 189 | End 190 | 191 | It dict 192 | let node = sfuncs._parse_expr('{ key : value }') 193 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 2) 194 | End 195 | 196 | It ternary 197 | let node = sfuncs._parse_expr('1 ? 2 : 3') 198 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 3) 199 | End 200 | 201 | It or 202 | let node = sfuncs._parse_expr('1 || 3') 203 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 2) 204 | End 205 | 206 | It not 207 | let node = sfuncs._parse_expr('!1') 208 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 1) 209 | End 210 | 211 | It plus 212 | let node = sfuncs._parse_expr('+1') 213 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 1) 214 | End 215 | 216 | It minus 217 | let node = sfuncs._parse_expr('-1') 218 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 1) 219 | End 220 | 221 | It subscript 222 | let node = sfuncs._parse_expr('x[y]') 223 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 2) 224 | End 225 | 226 | It slice x[y : z] 227 | let node = sfuncs._parse_expr('x[y : z]') 228 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 3) 229 | End 230 | 231 | It slice x[ : z] 232 | let node = sfuncs._parse_expr('x[ : z]') 233 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 2) 234 | End 235 | 236 | It slice x[y : ] 237 | let node = sfuncs._parse_expr('x[y : ]') 238 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 2) 239 | End 240 | 241 | It slice x[ : ] 242 | let node = sfuncs._parse_expr('x[ : ]') 243 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 1) 244 | End 245 | 246 | It . (dot) 247 | let node = sfuncs._parse_expr('x.y') 248 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 1) 249 | Assert Equals(sfuncs._next_evaluatable_node(node)[0].value, 'x') 250 | End 251 | 252 | It nested . (dot) 253 | let node = sfuncs._parse_expr('x.y.z') 254 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 1) 255 | Assert Equals(sfuncs._next_evaluatable_node(node)[0].left.value, 'x') 256 | Assert Equals(sfuncs._next_evaluatable_node(node)[0].right.value, 'y') 257 | End 258 | 259 | It call (with empty args) 260 | let node = sfuncs._parse_expr('x()') 261 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 0) 262 | End 263 | 264 | It call (with one arg) 265 | let node = sfuncs._parse_expr('x(y)') 266 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 1) 267 | Assert Equals(sfuncs._next_evaluatable_node(node)[0].value, 'y') 268 | End 269 | 270 | It call (with multiple args) 271 | let node = sfuncs._parse_expr('x(y, z)') 272 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 2) 273 | End 274 | 275 | It call (with curly name) 276 | let node = sfuncs._parse_expr('x{c}y()') 277 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 1) 278 | End 279 | 280 | It call (with dot) 281 | let node = sfuncs._parse_expr('x.y()') 282 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 1) 283 | End 284 | 285 | It curlyname 286 | let node = sfuncs._parse_expr('x{c}y') 287 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 1) 288 | End 289 | 290 | It nested curlyname 291 | let node = sfuncs._parse_expr('x{c{nest}}y') 292 | let next = sfuncs._next_evaluatable_node(node) 293 | Assert Equals(len(next), 1) 294 | let nested1 = sfuncs._next_evaluatable_node(next[0]) 295 | Assert Equals(len(nested1), 1) 296 | let nested2 = sfuncs._next_evaluatable_node(nested1[0]) 297 | Assert Equals(len(nested2), 1) 298 | Assert Equals(nested2[0].value.value, 'nest') 299 | End 300 | 301 | It curlynameexpr 302 | let node = sfuncs._parse_expr('{c}') 303 | Assert Equals(len(sfuncs._next_evaluatable_node(node)), 1) 304 | Assert Equals(sfuncs._next_evaluatable_node(node)[0].value.value, 'c') 305 | End 306 | End 307 | 308 | 309 | Describe ._compile_expr_with_pos 310 | It should compile expression with pos 311 | let node = sfuncs._parse_expr('1 ? 2 : 3') 312 | let expect = {'pos': {'lnum': 1, 'col': 3, 'i': 2}, 'expr': '(1 ? 2 : 3)'} 313 | Assert Equals(sfuncs._compile_expr_with_pos(node), expect) 314 | End 315 | End 316 | 317 | Describe ._node_to_eval_str 318 | It should return string node to eval 319 | let node = sfuncs._compile_expr_with_pos(sfuncs._parse_expr('1 ? 2 : 3')) 320 | let node_str = sfuncs._node_to_eval_str(node) 321 | let expect = "{'pos': {'lnum': 1, 'col': 3, 'i': 2}, 'expr': (1 ? 2 : 3)}" 322 | Assert IsString(node_str) 323 | Assert Equals(node_str, expect) 324 | End 325 | End 326 | 327 | Describe ._build_assertion_graph(whole_expr, evaluated_nodes) 328 | It should build graphical assertion result 329 | let x = { 'ary': [1, 'にーーー', 3, 0], 'power': 'assert' } 330 | let l:zero = 0 331 | let s:two = 2 332 | let threeeeee = 'さん3333' 333 | let expr = 'threeeeee is# (index(x.ary, l:zero) is# s:two)' 334 | 335 | let msg = [ 336 | \ "threeeeee is# (index(x.ary, l:zero) is# s:two)", 337 | \ "| | ||| | | |", 338 | \ "'さん3333' 0 3|| 0 0 2", 339 | \ " |[1, 'にーーー', 3, 0]", 340 | \ " {'ary': [1, 'にーーー', 3, 0], 'power': 'assert'}" 341 | \ ] 342 | 343 | let evaluated_nodes = map(sfuncs._aggregate_node_strs_to_eval(expr), 'eval(v:val)') 344 | Assert Equals(sfuncs._build_assertion_graph(expr, evaluated_nodes), msg) 345 | End 346 | End 347 | 348 | Describe ._build_line(nodes, sorted_cols) 349 | It should build line 350 | let nodes = [ 351 | \ {'pos': {'col': 1}, 'str_value': "'xxx'"}, 352 | \ {'pos': {'col': 10}, 'str_value': "'yyyy'"} 353 | \ ] 354 | let sorted_cols = [1, 7, 10, 18] 355 | let expect = "'xxx' | 'yyyy' |" 356 | Assert Equals(sfuncs._build_line(nodes, sorted_cols), expect) 357 | End 358 | End 359 | 360 | Describe ._compile 361 | It should compile expression node 362 | let node = sfuncs._parse_expr('1 ? 2 : 3') 363 | Assert Equals(sfuncs._compile(node), '(1 ? 2 : 3)') 364 | End 365 | End 366 | 367 | Describe ._parse_expr 368 | It should parse expr string 369 | let node = sfuncs._parse_expr('1 ? 2 : 3') 370 | Assert HasKey(node, 'type') 371 | End 372 | End 373 | 374 | Describe ._aggregate_node_strs_to_eval(expr_str) abort 375 | It should build nodes for argument of ._assert 376 | let x = { 'ary': [1, 'にーーー', 3, 0], 'power': 'assert' } 377 | let l:zero = 0 378 | let s:two = 2 379 | let threeeeee = 'さん3333' 380 | " 0 1 2 3 4 381 | " col: 1234567890123456789012345678901234567890123456 382 | let expr = 'threeeeee is# (index(x.ary, l:zero) is# s:two)' 383 | let result = map(sfuncs._aggregate_node_strs_to_eval(expr), 'eval(v:val)') 384 | Assert Equals(len(result), 8) 385 | let cols = sort([1, 12, 22, 23, 24, 30, 38, 42]) 386 | Assert Equals(sort(map(copy(result), 'v:val.pos.col')), cols) 387 | let exprs = sort([0, 0, 0, 2, 3, 'さん3333', [1, 'にーーー', 3, 0], { 'ary': [1, 'にーーー', 3, 0], 'power': 'assert' }]) 388 | Assert Equals(sort(map(copy(result), 'v:val.expr')), exprs) 389 | End 390 | End 391 | 392 | Describe ._aggregate_nodes_to_eval 393 | It should aggregates nodes to eval (which we want to inspect actual value) 394 | let nodes = sfuncs._aggregate_nodes_to_eval('1 ? x : y.z') 395 | Assert Equals(len(nodes), 4) 396 | Assert Equals(nodes[0].expr, '(1 ? x : y.z)') 397 | Assert Equals(nodes[1].expr, 'y.z') 398 | Assert Equals(nodes[2].expr, 'y') 399 | Assert Equals(nodes[3].expr, 'x') 400 | End 401 | End 402 | 403 | Describe expect 404 | Before all 405 | execute g:PowerAssert.define('PowerAssert') 406 | End 407 | 408 | let num = 1 409 | let str = 'foo' 410 | let xs = [1, 2, 3] 411 | let ys = [1, 2, [3, 4]] 412 | let ds = {'foo': 'x'} 413 | let nesteds = {'foo': {'x': 'y'}} 414 | 415 | let refxs = xs 416 | let refys = ys 417 | let refds = ds 418 | let refnesteds = nesteds 419 | 420 | Describe equal 421 | It should assert equal 422 | PowerAssert num == 1 423 | PowerAssert str == "foo" 424 | PowerAssert xs == [1, 2, 3] 425 | PowerAssert ys == [1, 2, [3, 4]] 426 | PowerAssert ds == {"foo": "x"} 427 | PowerAssert nesteds == {"foo": {"x": "y"}} 428 | End 429 | It should throws a report when values are different 430 | execute CheckThrow('num == 0') 431 | execute CheckThrow('str == "bar"') 432 | execute CheckThrow('xs == [1, 2, 3, 4]') 433 | execute CheckThrow('ys == [1, 2, []]') 434 | execute CheckThrow('ds == {"foo": "y"}') 435 | execute CheckThrow('nesteds == {"foo": {"x": "z"}}') 436 | End 437 | End 438 | 439 | Describe same 440 | It should assert same 441 | PowerAssert num is 1 442 | PowerAssert str is "foo" 443 | PowerAssert xs is refxs 444 | PowerAssert ys is refys 445 | PowerAssert ds is refds 446 | PowerAssert nesteds is refnesteds 447 | End 448 | It should throws a report when values are different 449 | execute CheckThrow('num is "1"') 450 | execute CheckThrow('str is "bar"') 451 | execute CheckThrow('xs is [1, 2, 3]') 452 | execute CheckThrow('ys is [1, 2, [3, 4]]') 453 | execute CheckThrow('ds is {"foo": "x"}') 454 | execute CheckThrow('nesteds is {"foo": {"x": "y"}}') 455 | End 456 | End 457 | 458 | Describe match 459 | It should assert match 460 | PowerAssert str =~# "oo" 461 | PowerAssert str !~# "vim" 462 | End 463 | It should throws a report when values are different 464 | execute CheckThrow('str !~# "oo"') 465 | execute CheckThrow('str =~# "vim"') 466 | End 467 | End 468 | 469 | Describe len 470 | It should assert len 471 | PowerAssert len(str) is# 3 472 | End 473 | It should throws a report when values are different 474 | execute CheckThrow('len(str) is# 444') 475 | End 476 | End 477 | 478 | Describe have_key 479 | It should assert have_key 480 | PowerAssert has_key(ds, str) 481 | End 482 | It should throws a report when values are different 483 | execute CheckThrow('has_key(ds, str . "xxx")') 484 | End 485 | End 486 | 487 | Describe type 488 | It should assert type 489 | PowerAssert type(str) is type('') 490 | End 491 | It should throws a report when values are different 492 | execute CheckThrow('type(ds) is type(1)') 493 | End 494 | End 495 | 496 | Describe float 497 | let f = 1.4 498 | It should assert float 499 | PowerAssert f is 1.4 500 | End 501 | It should throws a report when values are different 502 | execute CheckThrow('f is 1.5') 503 | End 504 | End 505 | 506 | Describe deep nested expression 507 | let f = 1.4 508 | let x = 'xxx' 509 | It should assert float 510 | PowerAssert f is 1.4 && x =~? 'x' || (len([1, 2, 3, { 'xxx': 'yyy'}]) ? x : f) =~# "xx" 511 | End 512 | It should throws a report when values are different 513 | " XXX: something wrong with CheckThrow 514 | Throws /vital: PowerAssert:/ :execute 515 | \ g:PowerAssert.assert( 516 | \ "f isnot 1.4 && x !~? 'x' || (len([1, 2, 3, { 'xxx': 'yyy'}]) ? x : f) is \"xx\"" 517 | \ ) 518 | End 519 | End 520 | 521 | Describe s:_parse_cmd_trail_msg() 522 | It should parse optional trailing message for :command 523 | let tests = [ 524 | \ { 525 | \ 'name': 'no msg', 526 | \ 'in': 'x == 1', 527 | \ 'want': ['x == 1', ''], 528 | \ }, 529 | \ { 530 | \ 'name': 'double quote', 531 | \ 'in': 'x == 1, "x is one (comment)"', 532 | \ 'want': ['x == 1', 'x is one (comment)'], 533 | \ }, 534 | \ { 535 | \ 'name': '\" in "', 536 | \ 'in': 'x == 1, "hoge \" foo"', 537 | \ 'want': ['x == 1', 'hoge \" foo'], 538 | \ }, 539 | \ { 540 | \ 'name': 'single quote', 541 | \ 'in': "x == 1, 'hoge foo'", 542 | \ 'want': ['x == 1', 'hoge foo'], 543 | \ }, 544 | \ { 545 | \ 'name': ', in function arg', 546 | \ 'in': "has_key({}, 'x'), 'hoge foo'", 547 | \ 'want': ['has_key({}, ''x'')', 'hoge foo'], 548 | \ }, 549 | \ { 550 | \ 'name': 'empty string', 551 | \ 'in': 'x == 1, ""', 552 | \ 'want': ['x == 1', ''], 553 | \ }, 554 | \ { 555 | \ 'name': 'quoted', 556 | \ 'in': "'x == 1, \"a\"'", 557 | \ 'want': ["'x == 1, \"a\"'", ''], 558 | \ }, 559 | \ ] 560 | for tt in tests 561 | let got = sfuncs._parse_cmd_trail_msg(tt.in) 562 | Assert Equals(got, tt.want, tt.name) 563 | endfor 564 | End 565 | End 566 | 567 | End 568 | 569 | End 570 | -------------------------------------------------------------------------------- /autoload/vital/_powerassert/Data/String.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not mofidify the code nor insert new lines before '" ___vital___' 4 | if v:version > 703 || v:version == 703 && has('patch1170') 5 | function! vital#_powerassert#Data#String#import() abort 6 | return map({'starts_with': '', 'split3': '', 'chop': '', 'unescape': '', 'split_posix_text': '', 'replace': '', 'scan': '', 'strchars': '', 'strwidthpart': '', 'common_head': '', 'reverse': '', 'escape_pattern': '', 'trim_end': '', '_vital_depends': '', 'wrap': '', 'join_posix_lines': '', 'contains_multibyte': '', 'truncate_skipping': '', 'split_leftright': '', 'ends_with': '', 'nsplit': '', 'substitute_last': '', 'strwidthpart_reverse': '', 'unescape_pattern': '', 'levenshtein_distance': '', 'trim_start': '', 'nr2hex': '', 'remove_ansi_sequences': '', 'iconv': '', 'pad_left': '', 'nr2enc_char': '', 'lines': '', 'repair_posix_text': '', 'nr2byte': '', 'trim': '', 'diffidx': '', 'truncate': '', 'split_by_displaywidth': '', 'padding_by_displaywidth': '', 'hash': '', 'chomp': '', 'pad_between_letters': '', 'wcswidth': '', 'dstring': '', 'pad_both_sides': '', 'justify_equal_spacing': '', 'pad_right': '', 'replace_first': '', '_vital_loaded': ''}, 'function("s:" . v:key)') 7 | endfunction 8 | else 9 | function! s:_SID() abort 10 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 11 | endfunction 12 | execute join(['function! vital#_powerassert#Data#String#import() abort', printf("return map({'starts_with': '', 'split3': '', 'chop': '', 'unescape': '', 'split_posix_text': '', 'replace': '', 'scan': '', 'strchars': '', 'strwidthpart': '', 'common_head': '', 'reverse': '', 'escape_pattern': '', 'trim_end': '', '_vital_depends': '', 'wrap': '', 'join_posix_lines': '', 'contains_multibyte': '', 'truncate_skipping': '', 'split_leftright': '', 'ends_with': '', 'nsplit': '', 'substitute_last': '', 'strwidthpart_reverse': '', 'unescape_pattern': '', 'levenshtein_distance': '', 'trim_start': '', 'nr2hex': '', 'remove_ansi_sequences': '', 'iconv': '', 'pad_left': '', 'nr2enc_char': '', 'lines': '', 'repair_posix_text': '', 'nr2byte': '', 'trim': '', 'diffidx': '', 'truncate': '', 'split_by_displaywidth': '', 'padding_by_displaywidth': '', 'hash': '', 'chomp': '', 'pad_between_letters': '', 'wcswidth': '', 'dstring': '', 'pad_both_sides': '', 'justify_equal_spacing': '', 'pad_right': '', 'replace_first': '', '_vital_loaded': ''}, \"function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 13 | delfunction s:_SID 14 | endif 15 | " ___vital___ 16 | " Utilities for string. 17 | 18 | let s:save_cpo = &cpo 19 | set cpo&vim 20 | 21 | function! s:_vital_loaded(V) abort 22 | let s:V = a:V 23 | let s:L = s:V.import('Data.List') 24 | endfunction 25 | 26 | function! s:_vital_depends() abort 27 | return ['Data.List'] 28 | endfunction 29 | 30 | " Substitute a:from => a:to by string. 31 | " To substitute by pattern, use substitute() instead. 32 | function! s:replace(str, from, to) abort 33 | return s:_replace(a:str, a:from, a:to, 'g') 34 | endfunction 35 | 36 | " Substitute a:from => a:to only once. 37 | " cf. s:replace() 38 | function! s:replace_first(str, from, to) abort 39 | return s:_replace(a:str, a:from, a:to, '') 40 | endfunction 41 | 42 | " implement of replace() and replace_first() 43 | function! s:_replace(str, from, to, flags) abort 44 | return substitute(a:str, '\V'.escape(a:from, '\'), escape(a:to, '\'), a:flags) 45 | endfunction 46 | 47 | function! s:scan(str, pattern) abort 48 | let list = [] 49 | call substitute(a:str, a:pattern, '\=add(list, submatch(0)) == [] ? "" : ""', 'g') 50 | return list 51 | endfunction 52 | 53 | function! s:reverse(str) abort 54 | return join(reverse(split(a:str, '.\zs')), '') 55 | endfunction 56 | 57 | function! s:starts_with(str, prefix) abort 58 | return stridx(a:str, a:prefix) == 0 59 | endfunction 60 | 61 | function! s:ends_with(str, suffix) abort 62 | let idx = strridx(a:str, a:suffix) 63 | return 0 <= idx && idx + len(a:suffix) == len(a:str) 64 | endfunction 65 | 66 | function! s:common_head(strs) abort 67 | if empty(a:strs) 68 | return '' 69 | endif 70 | let len = len(a:strs) 71 | if len == 1 72 | return a:strs[0] 73 | endif 74 | let strs = len == 2 ? a:strs : sort(copy(a:strs)) 75 | let pat = substitute(strs[0], '.', '\="[" . escape(submatch(0), "^\\") . "]"', 'g') 76 | return pat ==# '' ? '' : matchstr(strs[-1], '\C^\%[' . pat . ']') 77 | endfunction 78 | 79 | " Split to two elements of List. ([left, right]) 80 | " e.g.: s:split3('neocomplcache', 'compl') returns ['neo', 'compl', 'cache'] 81 | function! s:split_leftright(expr, pattern) abort 82 | let [left, _, right] = s:split3(a:expr, a:pattern) 83 | return [left, right] 84 | endfunction 85 | 86 | function! s:split3(expr, pattern) abort 87 | let ERROR = ['', '', ''] 88 | if a:expr ==# '' || a:pattern ==# '' 89 | return ERROR 90 | endif 91 | let begin = match(a:expr, a:pattern) 92 | if begin is -1 93 | return ERROR 94 | endif 95 | let end = matchend(a:expr, a:pattern) 96 | let left = begin <=# 0 ? '' : a:expr[: begin - 1] 97 | let right = a:expr[end :] 98 | return [left, a:expr[begin : end-1], right] 99 | endfunction 100 | 101 | " Slices into strings determines the number of substrings. 102 | " e.g.: s:nsplit("neo compl cache", 2, '\s') returns ['neo', 'compl cache'] 103 | function! s:nsplit(expr, n, ...) abort 104 | let pattern = get(a:000, 0, '\s') 105 | let keepempty = get(a:000, 1, 1) 106 | let ret = [] 107 | let expr = a:expr 108 | if a:n <= 1 109 | return [expr] 110 | endif 111 | while 1 112 | let pos = match(expr, pattern) 113 | if pos == -1 114 | if expr !~ pattern || keepempty 115 | call add(ret, expr) 116 | endif 117 | break 118 | elseif pos >= 0 119 | let left = pos > 0 ? expr[:pos-1] : '' 120 | if pos > 0 || keepempty 121 | call add(ret, left) 122 | endif 123 | let ml = len(matchstr(expr, pattern)) 124 | if pos == 0 && ml == 0 125 | let pos = 1 126 | endif 127 | let expr = expr[pos+ml :] 128 | endif 129 | if len(expr) == 0 130 | break 131 | endif 132 | if len(ret) == a:n - 1 133 | call add(ret, expr) 134 | break 135 | endif 136 | endwhile 137 | return ret 138 | endfunction 139 | 140 | " Returns the number of character in a:str. 141 | " NOTE: This returns proper value 142 | " even if a:str contains multibyte character(s). 143 | " s:strchars(str) {{{ 144 | if exists('*strchars') 145 | function! s:strchars(str) abort 146 | return strchars(a:str) 147 | endfunction 148 | else 149 | function! s:strchars(str) abort 150 | return strlen(substitute(copy(a:str), '.', 'x', 'g')) 151 | endfunction 152 | endif "}}} 153 | 154 | " Returns the bool of contains any multibyte character in s:str 155 | function! s:contains_multibyte(str) abort "{{{ 156 | return strlen(a:str) != s:strchars(a:str) 157 | endfunction "}}} 158 | 159 | " Remove last character from a:str. 160 | " NOTE: This returns proper value 161 | " even if a:str contains multibyte character(s). 162 | function! s:chop(str) abort "{{{ 163 | return substitute(a:str, '.$', '', '') 164 | endfunction "}}} 165 | 166 | " Remove last \r,\n,\r\n from a:str. 167 | function! s:chomp(str) abort "{{{ 168 | return substitute(a:str, '\%(\r\n\|[\r\n]\)$', '', '') 169 | endfunction "}}} 170 | 171 | " wrap() and its internal functions 172 | " * _split_by_wcswidth_once() 173 | " * _split_by_wcswidth() 174 | " * _concat() 175 | " * wrap() 176 | " 177 | " NOTE _concat() is just a copy of Data.List.concat(). 178 | " FIXME don't repeat yourself 179 | function! s:_split_by_wcswidth_once(body, x) abort 180 | let fst = s:strwidthpart(a:body, a:x) 181 | let snd = s:strwidthpart_reverse(a:body, s:wcswidth(a:body) - s:wcswidth(fst)) 182 | return [fst, snd] 183 | endfunction 184 | 185 | function! s:_split_by_wcswidth(body, x) abort 186 | let memo = [] 187 | let body = a:body 188 | while s:wcswidth(body) > a:x 189 | let [tmp, body] = s:_split_by_wcswidth_once(body, a:x) 190 | call add(memo, tmp) 191 | endwhile 192 | call add(memo, body) 193 | return memo 194 | endfunction 195 | 196 | function! s:trim(str) abort 197 | return matchstr(a:str,'^\s*\zs.\{-}\ze\s*$') 198 | endfunction 199 | 200 | function! s:trim_start(str) abort 201 | return matchstr(a:str,'^\s*\zs.\{-}$') 202 | endfunction 203 | 204 | function! s:trim_end(str) abort 205 | return matchstr(a:str,'^.\{-}\ze\s*$') 206 | endfunction 207 | 208 | function! s:wrap(str,...) abort 209 | let _columns = a:0 > 0 ? a:1 : &columns 210 | return s:L.concat( 211 | \ map(split(a:str, '\r\n\|[\r\n]'), 's:_split_by_wcswidth(v:val, _columns - 1)')) 212 | endfunction 213 | 214 | function! s:nr2byte(nr) abort 215 | if a:nr < 0x80 216 | return nr2char(a:nr) 217 | elseif a:nr < 0x800 218 | return nr2char(a:nr/64+192).nr2char(a:nr%64+128) 219 | else 220 | return nr2char(a:nr/4096%16+224).nr2char(a:nr/64%64+128).nr2char(a:nr%64+128) 221 | endif 222 | endfunction 223 | 224 | function! s:nr2enc_char(charcode) abort 225 | if &encoding ==# 'utf-8' 226 | return nr2char(a:charcode) 227 | endif 228 | let char = s:nr2byte(a:charcode) 229 | if strlen(char) > 1 230 | let char = strtrans(iconv(char, 'utf-8', &encoding)) 231 | endif 232 | return char 233 | endfunction 234 | 235 | function! s:nr2hex(nr) abort 236 | let n = a:nr 237 | let r = '' 238 | while n 239 | let r = '0123456789ABCDEF'[n % 16] . r 240 | let n = n / 16 241 | endwhile 242 | return r 243 | endfunction 244 | 245 | " If a ==# b, returns -1. 246 | " If a !=# b, returns first index of different character. 247 | function! s:diffidx(a, b) abort 248 | return a:a ==# a:b ? -1 : strlen(s:common_head([a:a, a:b])) 249 | endfunction 250 | 251 | function! s:substitute_last(expr, pat, sub) abort 252 | return substitute(a:expr, printf('.*\zs%s', a:pat), a:sub, '') 253 | endfunction 254 | 255 | function! s:dstring(expr) abort 256 | let x = substitute(string(a:expr), "^'\\|'$", '', 'g') 257 | let x = substitute(x, "''", "'", 'g') 258 | return printf('"%s"', escape(x, '"')) 259 | endfunction 260 | 261 | function! s:lines(str) abort 262 | return split(a:str, '\r\?\n') 263 | endfunction 264 | 265 | function! s:_pad_with_char(str, left, right, char) abort 266 | return repeat(a:char, a:left). a:str. repeat(a:char, a:right) 267 | endfunction 268 | 269 | function! s:pad_left(str, width, ...) abort 270 | let char = get(a:, 1, ' ') 271 | if strdisplaywidth(char) != 1 272 | throw "vital: Data.String: Can't use non-half-width characters for padding." 273 | endif 274 | let left = max([0, a:width - strdisplaywidth(a:str)]) 275 | return s:_pad_with_char(a:str, left, 0, char) 276 | endfunction 277 | 278 | function! s:pad_right(str, width, ...) abort 279 | let char = get(a:, 1, ' ') 280 | if strdisplaywidth(char) != 1 281 | throw "vital: Data.String: Can't use non-half-width characters for padding." 282 | endif 283 | let right = max([0, a:width - strdisplaywidth(a:str)]) 284 | return s:_pad_with_char(a:str, 0, right, char) 285 | endfunction 286 | 287 | function! s:pad_both_sides(str, width, ...) abort 288 | let char = get(a:, 1, ' ') 289 | if strdisplaywidth(char) != 1 290 | throw "vital: Data.String: Can't use non-half-width characters for padding." 291 | endif 292 | let space = max([0, a:width - strdisplaywidth(a:str)]) 293 | let left = space / 2 294 | let right = space - left 295 | return s:_pad_with_char(a:str, left, right, char) 296 | endfunction 297 | 298 | function! s:pad_between_letters(str, width, ...) abort 299 | let char = get(a:, 1, ' ') 300 | if strdisplaywidth(char) != 1 301 | throw "vital: Data.String: Can't use non-half-width characters for padding." 302 | endif 303 | let letters = split(a:str, '\zs') 304 | let each_width = a:width / len(letters) 305 | let str = join(map(letters, 's:pad_both_sides(v:val, each_width, char)'), '') 306 | if a:width - strdisplaywidth(str) > 0 307 | return char. s:pad_both_sides(str, a:width - 1, char) 308 | endif 309 | return str 310 | endfunction 311 | 312 | function! s:justify_equal_spacing(str, width, ...) abort 313 | let char = get(a:, 1, ' ') 314 | if strdisplaywidth(char) != 1 315 | throw "vital: Data.String: Can't use non-half-width characters for padding." 316 | endif 317 | let letters = split(a:str, '\zs') 318 | let first_letter = letters[0] 319 | " {width w/o the first letter} / {length w/o the first letter} 320 | let each_width = (a:width - strdisplaywidth(first_letter)) / (len(letters) - 1) 321 | let remainder = (a:width - strdisplaywidth(first_letter)) % (len(letters) - 1) 322 | return first_letter. join(s:L.concat([ 323 | \ map(letters[1:remainder], 's:pad_left(v:val, each_width + 1, char)'), 324 | \ map(letters[remainder + 1:], 's:pad_left(v:val, each_width, char)') 325 | \ ]), '') 326 | endfunction 327 | 328 | function! s:levenshtein_distance(str1, str2) abort 329 | let letters1 = split(a:str1, '\zs') 330 | let letters2 = split(a:str2, '\zs') 331 | let length1 = len(letters1) 332 | let length2 = len(letters2) 333 | let distances = map(range(1, length1 + 1), 'map(range(1, length2 + 1), ''0'')') 334 | 335 | for i1 in range(0, length1) 336 | let distances[i1][0] = i1 337 | endfor 338 | for i2 in range(0, length2) 339 | let distances[0][i2] = i2 340 | endfor 341 | 342 | for i1 in range(1, length1) 343 | for i2 in range(1, length2) 344 | let cost = (letters1[i1 - 1] ==# letters2[i2 - 1]) ? 0 : 1 345 | 346 | let distances[i1][i2] = min([ 347 | \ distances[i1 - 1][i2 ] + 1, 348 | \ distances[i1 ][i2 - 1] + 1, 349 | \ distances[i1 - 1][i2 - 1] + cost, 350 | \]) 351 | endfor 352 | endfor 353 | 354 | return distances[length1][length2] 355 | endfunction 356 | 357 | function! s:padding_by_displaywidth(expr, width, float) abort 358 | let padding_char = ' ' 359 | let n = a:width - strdisplaywidth(a:expr) 360 | if n <= 0 361 | let n = 0 362 | endif 363 | if a:float < 0 364 | return a:expr . repeat(padding_char, n) 365 | elseif 0 < a:float 366 | return repeat(padding_char, n) . a:expr 367 | else 368 | if n % 2 is 0 369 | return repeat(padding_char, n / 2) . a:expr . repeat(padding_char, n / 2) 370 | else 371 | return repeat(padding_char, (n - 1) / 2) . a:expr . repeat(padding_char, (n - 1) / 2) . padding_char 372 | endif 373 | endif 374 | endfunction 375 | 376 | function! s:split_by_displaywidth(expr, width, float, is_wrap) abort 377 | if a:width is 0 378 | return [''] 379 | endif 380 | 381 | let lines = [] 382 | 383 | let cs = split(a:expr, '\zs') 384 | let cs_index = 0 385 | 386 | let text = '' 387 | while cs_index < len(cs) 388 | if cs[cs_index] is# "\n" 389 | let text = s:padding_by_displaywidth(text, a:width, a:float) 390 | let lines += [text] 391 | let text = '' 392 | else 393 | let w = strdisplaywidth(text . cs[cs_index]) 394 | 395 | if w < a:width 396 | let text .= cs[cs_index] 397 | elseif a:width < w 398 | let text = s:padding_by_displaywidth(text, a:width, a:float) 399 | else 400 | let text .= cs[cs_index] 401 | endif 402 | 403 | if a:width <= w 404 | let lines += [text] 405 | let text = '' 406 | if a:is_wrap 407 | if a:width < w 408 | if a:width < strdisplaywidth(cs[cs_index]) 409 | while get(cs, cs_index, "\n") isnot# "\n" 410 | let cs_index += 1 411 | endwhile 412 | continue 413 | else 414 | let text = cs[cs_index] 415 | endif 416 | endif 417 | else 418 | while get(cs, cs_index, "\n") isnot# "\n" 419 | let cs_index += 1 420 | endwhile 421 | continue 422 | endif 423 | endif 424 | 425 | endif 426 | let cs_index += 1 427 | endwhile 428 | 429 | if !empty(text) 430 | let lines += [ s:padding_by_displaywidth(text, a:width, a:float) ] 431 | endif 432 | 433 | return lines 434 | endfunction 435 | 436 | function! s:hash(str) abort 437 | if exists('*sha256') 438 | return sha256(a:str) 439 | else 440 | " This gives up sha256ing but just adds up char with index. 441 | let sum = 0 442 | for i in range(len(a:str)) 443 | let sum += char2nr(a:str[i]) * (i + 1) 444 | endfor 445 | 446 | return printf('%x', sum) 447 | endif 448 | endfunction 449 | 450 | function! s:truncate(str, width) abort 451 | " Original function is from mattn. 452 | " http://github.com/mattn/googlereader-vim/tree/master 453 | 454 | if a:str =~# '^[\x00-\x7f]*$' 455 | return len(a:str) < a:width ? 456 | \ printf('%-'.a:width.'s', a:str) : strpart(a:str, 0, a:width) 457 | endif 458 | 459 | let ret = a:str 460 | let width = s:wcswidth(a:str) 461 | if width > a:width 462 | let ret = s:strwidthpart(ret, a:width) 463 | let width = s:wcswidth(ret) 464 | endif 465 | 466 | if width < a:width 467 | let ret .= repeat(' ', a:width - width) 468 | endif 469 | 470 | return ret 471 | endfunction 472 | 473 | function! s:truncate_skipping(str, max, footer_width, separator) abort 474 | let width = s:wcswidth(a:str) 475 | if width <= a:max 476 | let ret = a:str 477 | else 478 | let header_width = a:max - s:wcswidth(a:separator) - a:footer_width 479 | let ret = s:strwidthpart(a:str, header_width) . a:separator 480 | \ . s:strwidthpart_reverse(a:str, a:footer_width) 481 | endif 482 | return s:truncate(ret, a:max) 483 | endfunction 484 | 485 | function! s:strwidthpart(str, width) abort 486 | if a:width <= 0 487 | return '' 488 | endif 489 | let strarr = split(a:str, '\zs') 490 | let width = s:wcswidth(a:str) 491 | let index = len(strarr) 492 | let diff = (index + 1) / 2 493 | let rightindex = index - 1 494 | while width > a:width 495 | let index = max([rightindex - diff + 1, 0]) 496 | let partwidth = s:wcswidth(join(strarr[(index):(rightindex)], '')) 497 | if width - partwidth >= a:width || diff <= 1 498 | let width -= partwidth 499 | let rightindex = index - 1 500 | endif 501 | if diff > 1 502 | let diff = diff / 2 503 | endif 504 | endwhile 505 | return index ? join(strarr[:index - 1], '') : '' 506 | endfunction 507 | 508 | function! s:strwidthpart_reverse(str, width) abort 509 | if a:width <= 0 510 | return '' 511 | endif 512 | let strarr = split(a:str, '\zs') 513 | let width = s:wcswidth(a:str) 514 | let strlen = len(strarr) 515 | let diff = (strlen + 1) / 2 516 | let leftindex = 0 517 | let index = -1 518 | while width > a:width 519 | let index = min([leftindex + diff, strlen]) - 1 520 | let partwidth = s:wcswidth(join(strarr[(leftindex):(index)], '')) 521 | if width - partwidth >= a:width || diff <= 1 522 | let width -= partwidth 523 | let leftindex = index + 1 524 | endif 525 | if diff > 1 526 | let diff = diff / 2 527 | endif 528 | endwhile 529 | return index < strlen ? join(strarr[(index + 1):], '') : '' 530 | endfunction 531 | 532 | if v:version >= 703 533 | " Use builtin function. 534 | function! s:wcswidth(str) abort 535 | return strwidth(a:str) 536 | endfunction 537 | else 538 | function! s:wcswidth(str) abort 539 | if a:str =~# '^[\x00-\x7f]*$' 540 | return strlen(a:str) 541 | endif 542 | let mx_first = '^\(.\)' 543 | let str = a:str 544 | let width = 0 545 | while 1 546 | let ucs = char2nr(substitute(str, mx_first, '\1', '')) 547 | if ucs == 0 548 | break 549 | endif 550 | let width += s:_wcwidth(ucs) 551 | let str = substitute(str, mx_first, '', '') 552 | endwhile 553 | return width 554 | endfunction 555 | 556 | " UTF-8 only. 557 | function! s:_wcwidth(ucs) abort 558 | let ucs = a:ucs 559 | if (ucs >= 0x1100 560 | \ && (ucs <= 0x115f 561 | \ || ucs == 0x2329 562 | \ || ucs == 0x232a 563 | \ || (ucs >= 0x2e80 && ucs <= 0xa4cf 564 | \ && ucs != 0x303f) 565 | \ || (ucs >= 0xac00 && ucs <= 0xd7a3) 566 | \ || (ucs >= 0xf900 && ucs <= 0xfaff) 567 | \ || (ucs >= 0xfe30 && ucs <= 0xfe6f) 568 | \ || (ucs >= 0xff00 && ucs <= 0xff60) 569 | \ || (ucs >= 0xffe0 && ucs <= 0xffe6) 570 | \ || (ucs >= 0x20000 && ucs <= 0x2fffd) 571 | \ || (ucs >= 0x30000 && ucs <= 0x3fffd) 572 | \ )) 573 | return 2 574 | endif 575 | return 1 576 | endfunction 577 | endif 578 | 579 | function! s:remove_ansi_sequences(text) abort 580 | return substitute(a:text, '\e\[\%(\%(\d;\)\?\d\{1,2}\)\?[mK]', '', 'g') 581 | endfunction 582 | 583 | function! s:escape_pattern(str) abort 584 | " escape characters for no-magic 585 | return escape(a:str, '^$~.*[]\') 586 | endfunction 587 | 588 | function! s:unescape_pattern(str) abort 589 | " unescape characters for no-magic 590 | return s:unescape(a:str, '^$~.*[]\') 591 | endfunction 592 | 593 | function! s:unescape(str, chars) abort 594 | let chars = map(split(a:chars, '\zs'), 'escape(v:val, ''^$~.*[]\'')') 595 | return substitute(a:str, '\\\(' . join(chars, '\|') . '\)', '\1', 'g') 596 | endfunction 597 | 598 | function! s:iconv(expr, from, to) abort 599 | if a:from ==# '' || a:to ==# '' || a:from ==? a:to 600 | return a:expr 601 | endif 602 | let result = iconv(a:expr, a:from, a:to) 603 | return empty(result) ? a:expr : result 604 | endfunction 605 | 606 | " NOTE: 607 | " A definition of a TEXT file is "A file that contains characters organized 608 | " into one or more lines." 609 | " A definition of a LINE is "A sequence of zero or more non- s 610 | " plus a terminating " 611 | " That's why {stdin} always ends with ideally. However, there are 612 | " some programs which does not follow the POSIX rule and a Vim's way to join 613 | " List into TEXT; join({text}, "\n"); does not add to the end of 614 | " the last line. 615 | " That's why add a trailing if it does not exist. 616 | " REF: 617 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_392 618 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_205 619 | " :help split() 620 | " NOTE: 621 | " it does nothing if the text is a correct POSIX text 622 | function! s:repair_posix_text(text, ...) abort 623 | let newline = get(a:000, 0, "\n") 624 | return a:text =~# '\n$' ? a:text : a:text . newline 625 | endfunction 626 | 627 | " NOTE: 628 | " A definition of a TEXT file is "A file that contains characters organized 629 | " into one or more lines." 630 | " A definition of a LINE is "A sequence of zero or more non- s 631 | " plus a terminating " 632 | " REF: 633 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_392 634 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_205 635 | function! s:join_posix_lines(lines, ...) abort 636 | let newline = get(a:000, 0, "\n") 637 | return join(a:lines, newline) . newline 638 | endfunction 639 | 640 | " NOTE: 641 | " A definition of a TEXT file is "A file that contains characters organized 642 | " into one or more lines." 643 | " A definition of a LINE is "A sequence of zero or more non- s 644 | " plus a terminating " 645 | " TEXT into List; split({text}, '\r\?\n', 1); add an extra empty line at the 646 | " end of List because the end of TEXT ends with and keepempty=1 is 647 | " specified. (btw. keepempty=0 cannot be used because it will remove 648 | " emptylines in the head and the tail). 649 | " That's why removing a trailing before proceeding to 'split' is required 650 | " REF: 651 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_392 652 | " http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap03.html#tag_03_205 653 | function! s:split_posix_text(text, ...) abort 654 | let newline = get(a:000, 0, '\r\?\n') 655 | let text = substitute(a:text, newline . '$', '', '') 656 | return split(text, newline, 1) 657 | endfunction 658 | 659 | let &cpo = s:save_cpo 660 | unlet s:save_cpo 661 | " vim:set et ts=2 sts=2 sw=2 tw=0: 662 | --------------------------------------------------------------------------------