├── .gitignore ├── README.markdown ├── syntax └── vimcalc.vim ├── plugin ├── vimcalc.vim ├── tests.py └── vimcalc.py └── doc └── vimcalc.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | tags 4 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | #Readme 2 | 3 | ##Introduction 4 | 5 | VimCalc provides a convenient way to access a powerful interactive calculator 6 | whilst inside a Vim session. Quickly rattle off a few sums to test an idea. 7 | Perform complex calculations using built-in functions to validate answers. 8 | Quickly and simply convert from octal to hex to decimal and back again. Setup 9 | a bunch of variables to be used in a complex expression. Change variables 10 | easily and then simply re-evaluate the expression. Whatever you may need a 11 | calculator for, VimCalc is up to the task. 12 | 13 | Not only can VimCalc calculate but it uses Vim for input and editing. Quickly 14 | and easily perform edits on previous calculations using the power of Vim and 15 | then re-evaluate them by simply hitting return. Once you've got the answers yank 16 | them into several registers and paste with ease into other buffers! 17 | 18 | Here are some example expressions run in a VimCalc session: 19 | 20 | > 5+4 21 | ans = 9.0 22 | > let x = 4 23 | x = 4.0 24 | > 9 * sqrt(4) 25 | ans = 18.0 26 | > 9**2 - (sqrt(4)+2) 27 | ans = 77.0 28 | > 0xff + 2 29 | ans = 257.0 30 | 31 | Here is an example of calculating the roots of a quadratic 32 | equation: 33 | 34 | > let a = 2 35 | a = 2.0 36 | > let b = -1 37 | b = -1.0 38 | > let c = -6 39 | c = -6.0 40 | > ((b*-1) + sqrt(b**2 - 4*a*c))/(2*a) 41 | ans = 2.0 42 | > ((b*-1) - sqrt(b**2 - 4*a*c))/(2*a) 43 | ans = -1.5 44 | 45 | ##Installation 46 | 47 | ###Requirements 48 | 49 | * Vim 7.0+ with +python. 50 | * Python installed. 51 | 52 | If you're compiling Vim from source, be sure to use the --enable-pythoninterp 53 | option. Otherwise check your OS's package distro for a version of Vim with 54 | Python support. On OS X the best option is MacVim. VimCalc should work on 55 | Windows too, you will need to install the correct python dll for the version 56 | of Vim you are using. Please see the web for help with this. 57 | 58 | ###Installation 59 | 60 | Download the latest source from https://github.com/gregsexton/VimCalc. 61 | 62 | Extract (or copy) all of the files into your Vim runtime directory, ensuring 63 | that they are placed into the correct subdirectories. Then update your help 64 | tags file to enable VimCalc help. See :help add-local-help in Vim for details. 65 | -------------------------------------------------------------------------------- /syntax/vimcalc.vim: -------------------------------------------------------------------------------- 1 | " Vim syntax file 2 | "AUTHOR: Greg Sexton 3 | "WEBSITE: https://github.com/gregsexton/VimCalc 4 | "VERSION: 1.3, for Vim 7.0+ 5 | "LICENSE: Same terms as Vim itself (see :help license). 6 | 7 | if version < 600 8 | syntax clear 9 | elseif exists("b:current_syntax") 10 | finish 11 | endif 12 | 13 | syntax keyword vcalcAns ans 14 | syntax keyword vcalcE e 15 | syntax keyword vcalcPi pi 16 | syntax keyword vcalcPhi phi 17 | 18 | syntax keyword vcalcLet let 19 | 20 | syntax keyword vcalcFuncs abs acos asin atan atan2 ceil choose cos cosh deg exp floor hypot inv ldexp lg ln log log10 max min nrt perms pow rad rand round sin sinh sqrt tan tanh 21 | 22 | syntax match vcalcDirectives "\(:hex\|:oct\|:dec\|:int\|:float\|:status\|:s\|:vars\|:q\)\s*$" 23 | 24 | syntax match vcalcOps "\*\*=\|%=\|/=\|\*=\|-=\|+=\|<<\|>>\|\*\*\|=\|!\|%\|/\|\*\|-\|+" 25 | syntax match vcalcDelim "(\|)" 26 | 27 | syntax match vcalcDecNum "[0-9]\+\(\.[0-9]\+\)\?\(e[+-]\?[0-9]\+\)\?" 28 | syntax match vcalcHexNum "0x[0-9a-fA-F]\+" 29 | syntax match vcalcOctNum "0[0-7]\+" 30 | 31 | syntax match vcalcSynErr "^Syntax error: .*" 32 | syntax match vcalcParErr "^Parse error: .*" 33 | 34 | if g:VCalc_Prompt != '' 35 | silent execute "syn match vcalcPrompt '" . g:VCalc_Prompt . "'" 36 | hi def link vcalcPrompt Type 37 | endif 38 | 39 | syntax match vcalcDecDirOutput "CHANGED OUTPUT BASE TO DECIMAL." 40 | syntax match vcalcHexDirOutput "CHANGED OUTPUT BASE TO HEXADECIMAL." 41 | syntax match vcalcOctDirOutput "CHANGED OUTPUT BASE TO OCTAL." 42 | syntax match vcalcFloatDirOutput "CHANGED OUTPUT PRECISION TO FLOATING POINT." 43 | syntax match vcalcIntDirOutput "CHANGED OUTPUT PRECISION TO INTEGER." 44 | syntax match vcalcStatusVariables display contained "DECIMAL\|HEXADECIMAL\|OCTAL\|INTEGER\|FLOATING POINT" 45 | syntax region vcalcStatusDirOutput start="STATUS:" end="\." contains=vcalcStatusVariables 46 | 47 | syntax region vcalcVarsDirOutput start="^VARIABLES:$" end="^$" contains=vcalcDecNum,vcalcHexNum,vcalcOctNum 48 | 49 | if version >= 600 50 | command -nargs=+ HiLink highlight default link 51 | else 52 | command -nargs=+ HiLink highlight link 53 | endif 54 | 55 | "Special Symbols 56 | HiLink vcalcAns vcalcSymbol 57 | HiLink vcalcE vcalcSymbol 58 | HiLink vcalcPi vcalcSymbol 59 | HiLink vcalcPhi vcalcSymbol 60 | HiLink vcalcSymbol Constant 61 | 62 | "Keywords 63 | HiLink vcalcLet vcalcKeyword 64 | HiLink vcalcKeyword Keyword 65 | 66 | "Functions 67 | HiLink vcalcFuncs Function 68 | 69 | "Operators 70 | HiLink vcalcOps Operator 71 | 72 | "Delimiters 73 | HiLink vcalcDelim Delimiter 74 | 75 | "Directives 76 | HiLink vcalcDirectives Special 77 | 78 | "Numbers 79 | HiLink vcalcDecNum vcalcNumber 80 | HiLink vcalcHexNum vcalcNumber 81 | HiLink vcalcOctNum vcalcNumber 82 | HiLink vcalcNumber Number 83 | 84 | "Errors 85 | HiLink vcalcSynErr vcalcError 86 | HiLink vcalcParErr vcalcError 87 | HiLink vcalcError Error 88 | 89 | HiLink vcalcDecDirOutput vcalcDirOutput 90 | HiLink vcalcHexDirOutput vcalcDirOutput 91 | HiLink vcalcOctDirOutput vcalcDirOutput 92 | HiLink vcalcFloatDirOutput vcalcDirOutput 93 | HiLink vcalcIntDirOutput vcalcDirOutput 94 | HiLink vcalcStatusDirOutput vcalcDirOutput 95 | HiLink vcalcVarsDirOutput vcalcDirOutput 96 | HiLink vcalcDirOutput PreProc 97 | 98 | HiLink vcalcStatusVariables Statement 99 | 100 | delcommand HiLink 101 | 102 | let b:current_syntax = "vimcalc" 103 | -------------------------------------------------------------------------------- /plugin/vimcalc.vim: -------------------------------------------------------------------------------- 1 | "AUTHOR: Greg Sexton 2 | "WEBSITE: https://github.com/gregsexton/VimCalc 3 | "VERSION: 1.3, for Vim 7.0+ 4 | "LICENSE: Same terms as Vim itself (see :help license). 5 | 6 | "TODO: move most of the functionality to autoload script if gets more complicated 7 | 8 | if has('python') 9 | let scriptdirpy = expand(":h") . '/' 10 | exec "pyfile " . scriptdirpy . "vimcalc.py" 11 | endif 12 | 13 | if exists('g:loaded_vimcalc') || v:version < 700 14 | finish 15 | endif 16 | let g:loaded_vimcalc = 1 17 | 18 | "configurable options 19 | if !exists("g:VCalc_Title") 20 | let g:VCalc_Title = "__VCALC__" 21 | endif 22 | if !exists("g:VCalc_Prompt") 23 | let g:VCalc_Prompt = "> " 24 | endif 25 | if !exists("g:VCalc_Win_Size") 26 | let g:VCalc_Win_Size = 10 27 | endif 28 | if !exists("g:VCalc_Max_History") 29 | let g:VCalc_Max_History = 100 30 | endif 31 | if !exists("g:VCalc_CWInsert") 32 | let g:VCalc_CWInsert = 0 33 | endif 34 | if !exists("g:VCalc_InsertOnEnter") 35 | let g:VCalc_InsertOnEnter = 0 36 | endif 37 | if !exists("g:VCalc_WindowPosition") 38 | let g:VCalc_WindowPosition = 'top' "other possible values: left,right,bottom 39 | endif 40 | 41 | command! -nargs=0 -bar Calc call s:VCalc_Open() 42 | 43 | function! s:VCalc_Open() 44 | "validate 45 | let valid = VCalc_ValidateVim() 46 | if valid == -1 47 | return 48 | endif 49 | 50 | "if the window is open, jump to it 51 | let winnum = bufwinnr(g:VCalc_Title) 52 | if winnum != -1 53 | "jump to the existing window 54 | if winnr() != winnum 55 | exe winnum . 'wincmd w' 56 | endif 57 | return 58 | endif 59 | 60 | if g:VCalc_WindowPosition =~ "top\\|left" 61 | let position = 'aboveleft' 62 | else 63 | let position = 'rightbelow' 64 | endif 65 | 66 | "if the buffer does not already exist create otherwise edit. 67 | let bufnum = bufnr(g:VCalc_Title) 68 | if bufnum == -1 69 | if g:VCalc_WindowPosition =~ "left\\|right" 70 | let direction = 'vnew' 71 | else 72 | let direction = 'new' 73 | endif 74 | 75 | let wcmd = direction . ' ' . g:VCalc_Title 76 | exe 'silent ' . position . ' ' . g:VCalc_Win_Size . wcmd 77 | call setline(1, g:VCalc_Prompt) 78 | else 79 | if g:VCalc_WindowPosition =~ "left\\|right" 80 | let direction = 'vsplit' 81 | else 82 | let direction = 'split' 83 | endif 84 | 85 | let wcmd = direction . ' +buffer' . bufnum 86 | exe 'silent ' . position . ' ' . g:VCalc_Win_Size . wcmd 87 | call setline(line('$'), g:VCalc_Prompt) 88 | endif 89 | 90 | let b:VCalc_History = [] 91 | let b:VCalc_History_Index = -1 92 | 93 | call VCalc_SetLocalSettings() 94 | call VCalc_DefineMappingsAndAutoCommands() 95 | call VCalc_JumpToPrompt(1) 96 | endfunction 97 | 98 | function! s:VCalc_SetLocalSettings() 99 | silent! setlocal buftype=nofile 100 | silent! setlocal nobuflisted 101 | silent! setlocal noswapfile 102 | silent! setlocal bufhidden=delete 103 | silent! setlocal nonumber 104 | silent! setlocal nowrap 105 | setlocal filetype=vimcalc 106 | endfunction 107 | 108 | function! s:VCalc_DefineMappingsAndAutoCommands() 109 | nnoremap :call VCalc_REPL(0) 110 | inoremap :call VCalc_REPL(1) 111 | 112 | "inserting a new line jumps to the prompt 113 | nmap o :call VCalc_JumpToPrompt(1) 114 | nmap O :call VCalc_JumpToPrompt(1) 115 | 116 | nmap :help vimcalc-function-list 117 | 118 | imap :call VCalc_PreviousHistory() 119 | imap :call VCalc_NextHistory() 120 | 121 | au BufEnter :call VCalc_InsertOnEnter() 122 | 123 | call VCalc_CreateCWInsertMappings() 124 | endfunction 125 | 126 | function! s:VCalc_ValidateVim() 127 | if has('python') != 1 128 | echohl WarningMsg | echomsg "VCalc requires the Python interface to be installed." | echohl None 129 | return -1 130 | endif 131 | 132 | return 0 133 | endfunction 134 | 135 | function! s:VCalc_REPL(continueInsert) 136 | 137 | let expr = getline(".") 138 | if match(expr, g:VCalc_Prompt) != 0 139 | return 140 | else 141 | let expr = strpart(expr, matchend(expr, g:VCalc_Prompt)) 142 | endif 143 | 144 | call VCalc_RecordHistory(expr) 145 | "TODO: this breaks if a double quoted string is inputed. 146 | exe "python repl(\"" . expr . "\")" 147 | 148 | "if executed command don't continue -- may be a ':q' 149 | if exists("w:vcalc_vim_command") 150 | stopinsert 151 | return 152 | endif 153 | 154 | let failed = append(line('$'), g:VCalc_Prompt) 155 | 156 | let b:VCalc_History_Index = -1 157 | 158 | call VCalc_JumpToPrompt(a:continueInsert) 159 | endfunction 160 | 161 | function! s:VCalc_JumpToPrompt(withInsert) 162 | call setpos(".", [0, line('$'), col('$'), 0]) 163 | if a:withInsert == 1 164 | startinsert! 165 | endif 166 | endfunction 167 | 168 | function! s:VCalc_RecordHistory(expr) 169 | call insert(b:VCalc_History, a:expr) 170 | if len(b:VCalc_History) > g:VCalc_Max_History 171 | call remove(b:VCalc_History, -1) 172 | endif 173 | endfunction 174 | 175 | function! s:VCalc_PreviousHistory() 176 | if b:VCalc_History_Index < len(b:VCalc_History)-1 177 | let b:VCalc_History_Index += 1 178 | let failed = setline(line('$'), g:VCalc_Prompt . b:VCalc_History[b:VCalc_History_Index]) 179 | call VCalc_JumpToPrompt(1) 180 | endif 181 | endfunction 182 | 183 | function! s:VCalc_NextHistory() 184 | if b:VCalc_History_Index > 0 185 | let b:VCalc_History_Index -= 1 186 | let failed = setline(line('$'), g:VCalc_Prompt . b:VCalc_History[b:VCalc_History_Index]) 187 | call VCalc_JumpToPrompt(1) 188 | endif 189 | endfunction 190 | 191 | function! s:VCalc_InsertOnEnter() 192 | if g:VCalc_InsertOnEnter 193 | call VCalc_JumpToPrompt(1) 194 | endif 195 | endfunction 196 | 197 | function! s:VCalc_CreateCWInsertMappings() 198 | if g:VCalc_CWInsert 199 | imap l l 200 | imap k k 201 | imap j j 202 | imap h h 203 | imap b b 204 | imap t t 205 | imap w w 206 | imap W W 207 | "for lazy fingers: 208 | imap l 209 | imap k 210 | imap j 211 | imap h 212 | imap b 213 | imap t 214 | imap w 215 | imap W 216 | endif 217 | endfunction 218 | 219 | " ********************************************************************************************************** 220 | " **** PYTHON ********************************************************************************************** 221 | " ********************************************************************************************************** 222 | 223 | if has('python') 224 | 225 | python << EOF 226 | 227 | import vim 228 | 229 | def repl(expr): 230 | if expr != "": 231 | result = parse(expr) 232 | #if result is of the form: "!!!.*!!!" it is a vim command to execute. 233 | m = re.match(r"^!!!(.*)!!!$", result) 234 | if m: 235 | vim.command("call append(line('$'), g:VCalc_Prompt)") #add prompt 236 | vim.command(m.group(1)) 237 | vim.command("let w:vcalc_vim_command = 1") 238 | else: 239 | for str in result.split("\n"): 240 | vim.command("call append(line('$'), \"" + str + "\")") 241 | vim.command("if exists(\"w:vcalc_vim_command\") | unlet w:vcalc_vim_command | endif") 242 | EOF 243 | 244 | endif 245 | -------------------------------------------------------------------------------- /plugin/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import vimcalc 3 | import math 4 | 5 | class SanityCheckTestCase(unittest.TestCase): 6 | def runTest(self): 7 | assert vimcalc.parse("5*4") == "ans = 20.0", 'sanity check.' 8 | 9 | class OperatorTestCase(unittest.TestCase): 10 | def testAddition(self): 11 | assert vimcalc.parse("5+4") == "ans = 9.0", 'addition' 12 | 13 | def testSubtraction(self): 14 | assert vimcalc.parse("5-4") == "ans = 1.0", 'subtraction' 15 | 16 | def testMultiplication(self): 17 | assert vimcalc.parse("5*4") == "ans = 20.0", 'multiplication' 18 | 19 | def testDivision(self): 20 | assert vimcalc.parse("5/4") == "ans = 1.25", 'division' 21 | 22 | def testModulo(self): 23 | assert vimcalc.parse("5%4") == "ans = 1.0", 'modulo' 24 | assert vimcalc.parse("4%5") == "ans = 4.0", 'modulo' 25 | 26 | def testAnd(self): 27 | assert vimcalc.parse("5&3") == "ans = 1", 'bitwise and' 28 | assert vimcalc.parse("3&2") == "ans = 2", 'bitwise and' 29 | assert vimcalc.parse("6&13") == "ans = 4", 'bitwise and' 30 | 31 | def testOr(self): 32 | assert vimcalc.parse("5|3") == "ans = 7", 'bitwise or' 33 | 34 | def testXor(self): 35 | assert vimcalc.parse("5^3") == "ans = 6", 'bitwise xor' 36 | assert vimcalc.parse("2^10") == "ans = 8", 'bitwise xor' 37 | 38 | def testExponent(self): 39 | assert vimcalc.parse("5**4") == "ans = 625.0", 'exponent' 40 | 41 | def testLeftShift(self): 42 | assert vimcalc.parse("16<<1") == "ans = 32", 'left shift' 43 | assert vimcalc.parse("16<<2") == "ans = 64", 'left shift' 44 | 45 | def testRightShift(self): 46 | assert vimcalc.parse("16>>1") == "ans = 8", 'right shift' 47 | assert vimcalc.parse("16>>2") == "ans = 4", 'right shift' 48 | 49 | def testFactorial(self): 50 | assert vimcalc.parse("5!") == "ans = 120", 'factorial' 51 | 52 | def testComplicatedNested(self): 53 | assert vimcalc.parse("sin(sqrt(((pi/2)*2)**2)/2-(3-3))") == "ans = 1.0" 54 | 55 | class OperatorPrecedenceTestCase(unittest.TestCase): 56 | #this could do with being better in every way. 57 | def testAllPrecedenceAtOnce(self): 58 | assert vimcalc.parse("5*4+2/sin(pi/2)**2+-1") == "ans = 21.0" 59 | 60 | class OperatorAssociativityTestCase(unittest.TestCase): 61 | def testMultiplication(self): 62 | assert vimcalc.parse("2*2*2") == "ans = 8.0" 63 | assert vimcalc.parse("(2*2)*2") == "ans = 8.0" 64 | assert vimcalc.parse("2*(2*2)") == "ans = 8.0" 65 | 66 | def testDivision(self): 67 | assert vimcalc.parse("(2/2)/3") == "ans = 0.333333333333" 68 | assert vimcalc.parse("2/(2/3)") == "ans = 3.0" 69 | assert vimcalc.parse("2/2/3") == "ans = 0.333333333333" 70 | 71 | def testModulo(self): 72 | assert vimcalc.parse("(5%4)%3") == "ans = 1.0" 73 | assert vimcalc.parse("5%(4%3)") == "ans = 0.0" 74 | assert vimcalc.parse("5%4%3") == "ans = 1.0" 75 | 76 | def testLeftShift(self): 77 | assert vimcalc.parse("(8<<1)<<2") == "ans = 64" 78 | assert vimcalc.parse("8<<(1<<2)") == "ans = 128" 79 | assert vimcalc.parse("8<<1<<2") == "ans = 64" 80 | 81 | def testRightShift(self): 82 | assert vimcalc.parse("(16>>2)>>1") == "ans = 2" 83 | assert vimcalc.parse("16>>(2>>1)") == "ans = 8" 84 | assert vimcalc.parse("16>>1>>2") == "ans = 2" 85 | 86 | def testFactorial(self): 87 | assert vimcalc.parse("5!!") == "Parse error: the expression is invalid." 88 | assert vimcalc.parse("(3!)!") == "ans = 720" 89 | 90 | def testAddition(self): 91 | assert vimcalc.parse("(2+3)+4") == "ans = 9.0" 92 | assert vimcalc.parse("2+(3+4)") == "ans = 9.0" 93 | assert vimcalc.parse("2+3+4") == "ans = 9.0" 94 | 95 | def testSubtraction(self): 96 | assert vimcalc.parse("4-3-2") == "ans = -1.0" 97 | assert vimcalc.parse("(4-3)-2") == "ans = -1.0" 98 | assert vimcalc.parse("4-(3-2)") == "ans = 3.0" 99 | 100 | def testAnd(self): 101 | assert vimcalc.parse("(1&3)&9") == "ans = 1" 102 | assert vimcalc.parse("1&(3&9)") == "ans = 1" 103 | assert vimcalc.parse("1&3&9") == "ans = 1" 104 | 105 | def testOr(self): 106 | assert vimcalc.parse("(1|3)|9") == "ans = 11" 107 | assert vimcalc.parse("1|(3|9)") == "ans = 11" 108 | assert vimcalc.parse("1|3|9") == "ans = 11" 109 | 110 | def testXor(self): 111 | assert vimcalc.parse("(1^3)^9") == "ans = 11" 112 | assert vimcalc.parse("1^(3^9)") == "ans = 11" 113 | assert vimcalc.parse("1^3^9") == "ans = 11" 114 | 115 | #right-to-left 116 | def testExponent(self): 117 | assert vimcalc.parse("(2**2)**3") == "ans = 64.0" 118 | assert vimcalc.parse("2**(2**3)") == "ans = 256.0" 119 | assert vimcalc.parse("2**2**3") == "ans = 256.0" 120 | 121 | class AssignmentTestCase(unittest.TestCase): 122 | def testAssign(self): 123 | assert vimcalc.parse("let x = 2") == "x = 2.0", 'test assign.' 124 | assert vimcalc.parse("let x = 2.0") == "x = 2.0", 'test assign.' 125 | 126 | def testAssignNoLet(self): 127 | assert vimcalc.parse("x = 2") == "x = 2.0", 'test assign.' 128 | assert vimcalc.parse("x = 2.0") == "x = 2.0", 'test assign.' 129 | 130 | def testUsingAssigned(self): 131 | vimcalc.parse("let a = 2") 132 | vimcalc.parse("let b = 8") 133 | vimcalc.parse("x = 2") 134 | vimcalc.parse("y = 8") 135 | assert vimcalc.parse("a + b") == "ans = 10.0", 'test using assignment' 136 | assert vimcalc.parse("x + 2") == "ans = 4.0", 'test using assignment' 137 | assert vimcalc.parse("x + y") == "ans = 10.0", 'test using assignment' 138 | 139 | def testAssignUsingAssigned(self): 140 | vimcalc.parse("x = 4") 141 | vimcalc.parse("y = 5") 142 | assert vimcalc.parse("let z = x * y") == "z = 20.0", 'test assign assigned.' 143 | 144 | def testPAssign(self): 145 | vimcalc.parse("x = 4") 146 | vimcalc.parse("y = 5") 147 | assert vimcalc.parse("let x += y") == "x = 9.0" 148 | 149 | def testSAssign(self): 150 | vimcalc.parse("x = 4") 151 | vimcalc.parse("y = 5") 152 | assert vimcalc.parse("let y -= x") == "y = 1.0" 153 | 154 | def testMAssign(self): 155 | vimcalc.parse("x = 4") 156 | vimcalc.parse("y = 5") 157 | assert vimcalc.parse("let x *= y") == "x = 20.0" 158 | 159 | def testDAssign(self): 160 | vimcalc.parse("x = 4") 161 | vimcalc.parse("y = 5") 162 | assert vimcalc.parse("let x /= y") == "x = 0.8" 163 | 164 | def testModAssign(self): 165 | vimcalc.parse("x = 4") 166 | vimcalc.parse("y = 5") 167 | assert vimcalc.parse("let x %= y") == "x = 4.0" 168 | vimcalc.parse("x = 4") 169 | vimcalc.parse("y = 5") 170 | assert vimcalc.parse("let y %= x") == "y = 1.0" 171 | 172 | def testAndAssign(self): 173 | vimcalc.parse("x = 5") 174 | vimcalc.parse("y = 3") 175 | assert vimcalc.parse("let x &= y") == "x = 1" 176 | vimcalc.parse("x = 3") 177 | vimcalc.parse("y = 2") 178 | assert vimcalc.parse("let x &= y") == "x = 2" 179 | vimcalc.parse("x = 6") 180 | vimcalc.parse("y = 13") 181 | assert vimcalc.parse("let x &= y") == "x = 4" 182 | 183 | def testOrAssign(self): 184 | vimcalc.parse("x = 5") 185 | vimcalc.parse("y = 3") 186 | assert vimcalc.parse("let x |= y") == "x = 7" 187 | 188 | def testXorAssign(self): 189 | vimcalc.parse("x = 5") 190 | vimcalc.parse("y = 3") 191 | assert vimcalc.parse("let x ^= y") == "x = 6" 192 | vimcalc.parse("x = 2") 193 | vimcalc.parse("y = 10") 194 | assert vimcalc.parse("let x ^= y") == "x = 8" 195 | 196 | def testExpAssign(self): 197 | vimcalc.parse("x = 4") 198 | vimcalc.parse("y = 5") 199 | assert vimcalc.parse("let x **= y") == "x = 1024.0" 200 | 201 | class ConstantsTestCase(unittest.TestCase): 202 | def testConstants(self): 203 | assert vimcalc.parse("e") == "ans = 2.71828182846" 204 | assert vimcalc.parse("pi") == "ans = 3.14159265359" 205 | assert vimcalc.parse("phi") == "ans = 1.61803398875" 206 | 207 | class BasesTestCase(unittest.TestCase): 208 | def tearDown(self): 209 | vimcalc.parse(":dec") 210 | 211 | def testDecimal(self): 212 | assert vimcalc.parse(":dec") == "CHANGED OUTPUT BASE TO DECIMAL." 213 | assert vimcalc.parse("10") == "ans = 10.0" 214 | assert vimcalc.parse("0x10") == "ans = 16" 215 | assert vimcalc.parse("010") == "ans = 8" 216 | 217 | def testHexadecimal(self): 218 | assert vimcalc.parse(":hex") == "CHANGED OUTPUT BASE TO HEXADECIMAL." 219 | assert vimcalc.parse("10") == "ans = 0xa" 220 | assert vimcalc.parse("0x10") == "ans = 0x10" 221 | assert vimcalc.parse("010") == "ans = 0x8" 222 | 223 | def testOctal(self): 224 | assert vimcalc.parse(":oct") == "CHANGED OUTPUT BASE TO OCTAL." 225 | assert vimcalc.parse("10") == "ans = 012" 226 | assert vimcalc.parse("0x10") == "ans = 020" 227 | assert vimcalc.parse("010") == "ans = 010" 228 | 229 | class PrecisionTestCase(unittest.TestCase): 230 | def tearDown(self): 231 | vimcalc.parse(":float") 232 | 233 | def testInteger(self): 234 | assert vimcalc.parse(":int") == "CHANGED OUTPUT PRECISION TO INTEGER." 235 | assert vimcalc.parse("(8/3) * (4/3)") == "ans = 2" 236 | 237 | def testFloat(self): 238 | assert vimcalc.parse(":float") == "CHANGED OUTPUT PRECISION TO FLOATING POINT." 239 | assert vimcalc.parse("(8/3) * (4/3)") == "ans = 3.55555555556" 240 | 241 | class VarListingTestCase(unittest.TestCase): 242 | def setUp(self): 243 | self.resetSymbolTable() 244 | 245 | def tearDown(self): 246 | vimcalc.parse(":float") 247 | vimcalc.parse(":dec") 248 | 249 | def resetSymbolTable(self): 250 | temp = { 'ans' : 0, 251 | 'e' : vimcalc.VCALC_SYMBOL_TABLE['e'], 252 | 'pi' : vimcalc.VCALC_SYMBOL_TABLE['pi'], 253 | 'phi' : vimcalc.VCALC_SYMBOL_TABLE['phi'] } 254 | vimcalc.VCALC_SYMBOL_TABLE = temp 255 | 256 | def testSanity(self): 257 | assert len(vimcalc.VCALC_SYMBOL_TABLE) == 4 258 | assert vimcalc.VCALC_SYMBOL_TABLE['ans'] == 0 259 | assert vimcalc.VCALC_SYMBOL_TABLE['e'] == math.e 260 | assert vimcalc.VCALC_SYMBOL_TABLE['pi'] == math.pi 261 | assert vimcalc.VCALC_SYMBOL_TABLE['phi'] == 1.6180339887498948482 262 | 263 | def testDefault(self): 264 | assert vimcalc.parse(":vars") == "VARIABLES:\n----------\n ans : 0\n e : 2.71828182846\n phi : 1.61803398875\n pi : 3.14159265359\n" 265 | 266 | def testAddVars(self): 267 | #tests they get added and alphabetically 268 | assert vimcalc.parse("x = 2") == "x = 2.0" 269 | assert vimcalc.parse(":vars") == "VARIABLES:\n----------\n ans : 0\n e : 2.71828182846\n phi : 1.61803398875\n pi : 3.14159265359\n x : 2.0\n" 270 | assert vimcalc.parse("a = 2") == "a = 2.0" 271 | assert vimcalc.parse(":vars") == "VARIABLES:\n----------\n a : 2.0\n ans : 0\n e : 2.71828182846\n phi : 1.61803398875\n pi : 3.14159265359\n x : 2.0\n" 272 | 273 | def testChangeMode(self): 274 | vimcalc.parse(":dec") 275 | assert vimcalc.parse(":vars") == "VARIABLES:\n----------\n ans : 0\n e : 2.71828182846\n phi : 1.61803398875\n pi : 3.14159265359\n" 276 | vimcalc.parse(":hex") 277 | assert vimcalc.parse(":vars") == "VARIABLES:\n----------\n ans : 0x0\n e : 0x2\n phi : 0x1\n pi : 0x3\n" 278 | vimcalc.parse(":oct") 279 | assert vimcalc.parse(":vars") == "VARIABLES:\n----------\n ans : 0\n e : 02\n phi : 01\n pi : 03\n" 280 | 281 | def testChangePrecision(self): 282 | vimcalc.parse(":float") 283 | assert vimcalc.parse(":vars") == "VARIABLES:\n----------\n ans : 0\n e : 2.71828182846\n phi : 1.61803398875\n pi : 3.14159265359\n" 284 | vimcalc.parse(":int") 285 | assert vimcalc.parse(":vars") == "VARIABLES:\n----------\n ans : 0\n e : 2\n phi : 1\n pi : 3\n" 286 | 287 | def testAlignment(self): 288 | assert vimcalc.parse("let reallyLongName = 2") == "reallyLongName = 2.0" 289 | assert vimcalc.parse(":vars") == "VARIABLES:\n----------\n ans : 0\n e : 2.71828182846\n phi : 1.61803398875\n pi : 3.14159265359\n reallyLongName : 2.0\n" 290 | 291 | class MiscDirectivesTestCase(unittest.TestCase): 292 | def setUp(self): 293 | vimcalc.parse(":dec") 294 | vimcalc.parse(":float") 295 | 296 | def tearDown(self): 297 | vimcalc.parse(":dec") 298 | vimcalc.parse(":float") 299 | 300 | def testStatusSanity(self): 301 | assert vimcalc.parse(":status") != "Syntax error: :status" 302 | 303 | def testStatus(self): 304 | assert vimcalc.parse(":status") == "STATUS: OUTPUT BASE: DECIMAL; PRECISION: FLOATING POINT." 305 | vimcalc.parse(":hex") 306 | assert vimcalc.parse(":status") == "STATUS: OUTPUT BASE: HEXADECIMAL; PRECISION: FLOATING POINT." 307 | vimcalc.parse(":oct") 308 | assert vimcalc.parse(":status") == "STATUS: OUTPUT BASE: OCTAL; PRECISION: FLOATING POINT." 309 | vimcalc.parse(":int") 310 | vimcalc.parse(":dec") 311 | assert vimcalc.parse(":status") == "STATUS: OUTPUT BASE: DECIMAL; PRECISION: INTEGER." 312 | vimcalc.parse(":hex") 313 | assert vimcalc.parse(":status") == "STATUS: OUTPUT BASE: HEXADECIMAL; PRECISION: INTEGER." 314 | vimcalc.parse(":oct") 315 | assert vimcalc.parse(":status") == "STATUS: OUTPUT BASE: OCTAL; PRECISION: INTEGER." 316 | 317 | def testStatusShorthand(self): 318 | assert vimcalc.parse(":s") == "STATUS: OUTPUT BASE: DECIMAL; PRECISION: FLOATING POINT." 319 | vimcalc.parse(":hex") 320 | assert vimcalc.parse(":s") == "STATUS: OUTPUT BASE: HEXADECIMAL; PRECISION: FLOATING POINT." 321 | vimcalc.parse(":oct") 322 | assert vimcalc.parse(":s") == "STATUS: OUTPUT BASE: OCTAL; PRECISION: FLOATING POINT." 323 | vimcalc.parse(":int") 324 | vimcalc.parse(":dec") 325 | assert vimcalc.parse(":s") == "STATUS: OUTPUT BASE: DECIMAL; PRECISION: INTEGER." 326 | vimcalc.parse(":hex") 327 | assert vimcalc.parse(":s") == "STATUS: OUTPUT BASE: HEXADECIMAL; PRECISION: INTEGER." 328 | vimcalc.parse(":oct") 329 | assert vimcalc.parse(":s") == "STATUS: OUTPUT BASE: OCTAL; PRECISION: INTEGER." 330 | 331 | def testQuitDirective(self): 332 | assert vimcalc.parse(":q") == "!!!q!!!" 333 | 334 | class ErrorMessagesTestCase(unittest.TestCase): 335 | def testNonExistantBuiltin(self): 336 | assert vimcalc.parse("foo()") == "Parse error: built-in function 'foo' does not exist." 337 | 338 | def testUnmatchedParens(self): 339 | assert vimcalc.parse("(5") == "Parse error: missing matching parenthesis in expression." 340 | assert vimcalc.parse("((5)") == "Parse error: missing matching parenthesis in expression." 341 | assert vimcalc.parse("(()") == "Parse error: the expression is invalid." 342 | 343 | def testUndefinedSymbol(self): 344 | assert vimcalc.parse("thisshouldnotexist") == "Parse error: symbol 'thisshouldnotexist' is not defined." 345 | 346 | def testSyntaxError(self): 347 | assert vimcalc.parse("\"string\"") == "Syntax error: \"string\"" 348 | assert vimcalc.parse("'string'") == "Syntax error: 'string'" 349 | 350 | def testParseError(self): 351 | assert vimcalc.parse("9**5/)") == "Parse error: the expression is invalid." 352 | assert vimcalc.parse("4//5") == "Parse error: the expression is invalid." 353 | assert vimcalc.parse("--1") == "Parse error: the expression is invalid." 354 | assert vimcalc.parse("!4") == "Parse error: the expression is invalid." 355 | assert vimcalc.parse("2***3") == "Parse error: the expression is invalid." 356 | assert vimcalc.parse("sin(2,)") == "Parse error: apply() arg 2 expected sequence, found int" 357 | 358 | class FunctionsTestCase(unittest.TestCase): 359 | def testAbs(self): 360 | assert vimcalc.parse("abs(-4.2)") == "ans = 4.2", 'test abs(x)' 361 | def testAcos(self): 362 | assert vimcalc.parse("acos(1)") == "ans = 0.0", 'test acos(x)' 363 | def testAsin(self): 364 | assert vimcalc.parse("asin(0)") == "ans = 0.0", 'test asin(x)' 365 | def testAtan(self): 366 | assert vimcalc.parse("atan(0)") == "ans = 0.0", 'test atan(x)' 367 | def testAtan2(self): 368 | assert vimcalc.parse("atan2(1,1)") == "ans = 0.785398163397", 'test atan2(y,x)' 369 | def testCeil(self): 370 | assert vimcalc.parse("ceil(4.2)") == "ans = 5.0", 'test ceil(x)' 371 | def testChoose(self): 372 | assert vimcalc.parse("choose(3,2)") == "ans = 3", 'test choose(n,k)' 373 | assert vimcalc.parse("choose(3,2.2)") == "ans = 3", 'test choose(n,k)' 374 | def testCos(self): 375 | assert vimcalc.parse("cos(0)") == "ans = 1.0", 'test cos(x)' 376 | #TODO: 377 | def testCosh(self): 378 | assert vimcalc.parse("cosh(1)") == "ans = 1.54308063482", 'test cosh(x)' 379 | def testDeg(self): 380 | assert vimcalc.parse("deg(pi)") == "ans = 180.0", 'test deg(x)' 381 | assert vimcalc.parse("deg(pi/2)") == "ans = 90.0", 'test deg(x)' 382 | assert vimcalc.parse("deg(2*pi)") == "ans = 360.0", 'test deg(x)' 383 | assert vimcalc.parse("deg(2*pi+1)") == "ans = 417.295779513", 'test deg(x)' 384 | def testExp(self): 385 | assert vimcalc.parse("exp(1)/e") == "ans = 1.0", 'test exp(x)' 386 | def testFloor(self): 387 | assert vimcalc.parse("floor(4.7)") == "ans = 4.0", 'test floor(x)' 388 | def testHypot(self): 389 | assert vimcalc.parse("hypot(3,4)") == "ans = 5.0", 'test hypot(x,y)' 390 | def testInv(self): 391 | assert vimcalc.parse("inv(2)") == "ans = 0.5", 'test inv(x)' 392 | #TODO: 393 | #def testLdexp(self): 394 | #assert vimcalc.parse("ldexp(2,2)") == "ans = ", 'test ldexp(x,i)' 395 | def testLg(self): 396 | assert vimcalc.parse("lg(0)") == "Parse error: math domain error", 'test lg(x)' 397 | assert vimcalc.parse("lg(1)") == "ans = 0.0", 'test lg(x)' 398 | assert vimcalc.parse("lg(4)") == "ans = 2.0", 'test lg(x)' 399 | def testLn(self): 400 | assert vimcalc.parse("ln(0)") == "Parse error: math domain error", 'test ln(x)' 401 | assert vimcalc.parse("ln(1)") == "ans = 0.0", 'test ln(x)' 402 | assert vimcalc.parse("ln(e)") == "ans = 1.0", 'test ln(x)' 403 | def testLog(self): 404 | assert vimcalc.parse("log(9,3)") == "ans = 2.0", 'test log(x,b)' 405 | assert vimcalc.parse("log(1,3)") == "ans = 0.0", 'test log(x,b)' 406 | assert vimcalc.parse("log(0,3)") == "Parse error: math domain error", 'test log(x,b)' 407 | def testLog10(self): 408 | assert vimcalc.parse("log10(100)") == "ans = 2.0", 'test log10(x)' 409 | assert vimcalc.parse("log10(1)") == "ans = 0.0", 'test log10(x)' 410 | assert vimcalc.parse("log10(0)") == "Parse error: math domain error", 'test log10(x)' 411 | def testMax(self): 412 | assert vimcalc.parse("max(3,7)") == "ans = 7.0", 'test max(x,y)' 413 | assert vimcalc.parse("max(3.2,7.6)") == "ans = 7.6", 'test max(x,y)' 414 | def testMin(self): 415 | assert vimcalc.parse("min(3,7)") == "ans = 3.0", 'test min(x,y)' 416 | assert vimcalc.parse("min(3.2,7.6)") == "ans = 3.2", 'test min(x,y)' 417 | def testNrt(self): 418 | assert vimcalc.parse("nrt(27,3)") == "ans = 3.0", 'test nrt(x,n)' 419 | def testPerms(self): 420 | assert vimcalc.parse("perms(3,2)") == "ans = 6", 'test perms(n,k)' 421 | def testPow(self): 422 | assert vimcalc.parse("pow(2,5)") == "ans = 32.0", 'test pow(x,y)' 423 | def testRad(self): 424 | assert vimcalc.parse("rad(0)") == "ans = 0.0", 'test rad(x)' 425 | assert vimcalc.parse("rad(180)") == "ans = 3.14159265359", 'test rad(x)' 426 | assert vimcalc.parse("rad(360)") == "ans = 6.28318530718", 'test rad(x)' 427 | #def testRand(self): 428 | #assert vimcalc.parse("rand()") == "ans = ", 'test rand()' 429 | def testRound(self): 430 | assert vimcalc.parse("round(4.2)") == "ans = 4.0", 'test round(x)' 431 | assert vimcalc.parse("round(4.7)") == "ans = 5.0", 'test round(x)' 432 | def testSin(self): 433 | assert vimcalc.parse("sin(pi/2)") == "ans = 1.0", 'test sin.' 434 | assert vimcalc.parse("sin(0)") == "ans = 0.0", 'test sin.' 435 | #TODO: 436 | def testSinh(self): 437 | assert vimcalc.parse("sinh(0)") == "ans = 0.0", 'test sinh(x)' 438 | def testSqrt(self): 439 | assert vimcalc.parse("sqrt(64)") == "ans = 8.0", 'test sqrt(x)' 440 | assert vimcalc.parse("sqrt(0)") == "ans = 0.0", 'test sqrt(x)' 441 | assert vimcalc.parse("sqrt(2)") == "ans = 1.41421356237", 'test sqrt(x)' 442 | def testTan(self): 443 | assert vimcalc.parse("tan(0)") == "ans = 0.0", 'test tan(x)' 444 | def testTanh(self): 445 | assert vimcalc.parse("tanh(0)") == "ans = 0.0", 'test tanh(x)' 446 | 447 | if __name__ == "__main__": 448 | unittest.main() 449 | -------------------------------------------------------------------------------- /plugin/vimcalc.py: -------------------------------------------------------------------------------- 1 | #AUTHOR: Greg Sexton 2 | #WEBSITE: https://github.com/gregsexton/VimCalc 3 | #VERSION: 1.3, for Vim 7.0+ 4 | #LICENSE: Same terms as Vim itself (see :help license). 5 | 6 | import math, re, random 7 | 8 | #### LEXICAL ANALYSIS FUNCTIONS ################################################## 9 | 10 | #lexemes 11 | #digit = [0-9] 12 | #digits = digit+ 13 | 14 | #uppercase = [A-Z] 15 | #lowercase = [a-z] 16 | 17 | #alpha = (uppercase|lowercase) 18 | #alphanumeric = (alpha|digits) 19 | 20 | #hexdigit = [0-9a-fA-F] 21 | #hexdigits = hexdigit+ 22 | 23 | #octdigit = [0-7] 24 | #octdigits = octdigit+ 25 | 26 | #decnumber = digits(. digits)?(e[+-]? digits)? 27 | #hexnumber = 0xhexdigits 28 | #octalnumber = 0 octdigits 29 | 30 | #whitespace = [\t ]+ 31 | 32 | #ident = (alpha|_)(alphanumeric|_)*'? 33 | 34 | #plus = '+' 35 | #subtract = '-' 36 | #multiply = '*' 37 | #divide = '/' 38 | #modulo = '%' 39 | #exponent = '**' 40 | #lShift = '<<' 41 | #rShift = '>>' 42 | #factorial = '!' 43 | 44 | #unaryOp = factorial 45 | #binaryOp = plus|subtract|multiply|divide|modulo|exponent|lShift|rShift 46 | #operator = unaryOp|binaryOp 47 | 48 | #lParen = '(' 49 | #rParen = ')' 50 | #comma = ',' 51 | #assign = '=' 52 | #pAssign = '+=' 53 | #sAssign = '-=' 54 | #mAssign = '*=' 55 | #dAssign = '/=' 56 | #modAssign = '%=' 57 | #expAssign = '**=' 58 | 59 | #delimiters = lParen|rParen|comma|assign|pAssign|sAssign|mAssign|dAssign|modAssign|expAssign 60 | 61 | #let = 'let' 62 | #keywords = let 63 | 64 | #decDir = ':dec' 65 | #hexDir = ':hex' 66 | #octDir = ':oct' 67 | #intDir = ':int' 68 | #floatDir = ':float' 69 | #statusDir = ':status' | ':s' 70 | #varDir = ':vars' 71 | #quitDir = ':q' 72 | #directives = decDir | hexDir | octDir | intDir | floatDir | statusDir | varDir | quitDir 73 | 74 | class Token(object): 75 | def __init__(self, tokenID, attrib): 76 | self._tokenID = tokenID 77 | self._attrib = attrib 78 | def __repr__(self): 79 | return str(self._tokenID) + ':' + str(self._attrib) 80 | def __str__(self): 81 | return str(self._tokenID) + ':' + str(self._attrib) 82 | def getID(self): 83 | return self._tokenID 84 | def getAttrib(self): 85 | return self._attrib 86 | ID = property(getID, doc='Token ID [string].') 87 | attrib = property(getAttrib, doc='Token attribute [string].') 88 | 89 | class Lexeme(object): 90 | def __init__(self, identifier, regex): 91 | self._ID = identifier 92 | self._regex = regex 93 | def getID(self): 94 | return self._ID 95 | def getRegex(self): 96 | return self._regex 97 | ID = property(getID, doc='Lexeme ID [string].') 98 | regex = property(getRegex, doc='Regex to match the Lexeme.') 99 | 100 | #language lexemes NOTE: don't change these without changing syntax file 101 | lexemes = [Lexeme('whitespace', r'\s+'), 102 | Lexeme('hexnumber', r'0x[0-9a-fA-F]+'), 103 | Lexeme('octnumber', r'0[0-7]+'), 104 | Lexeme('decnumber', r'[0-9]+(\.[0-9]+)?(e[+-]?[0-9]+)?'), 105 | Lexeme('let', r'let'), 106 | Lexeme('ident', r"[A-Za-z_][A-Za-z0-9_]*'?"), 107 | Lexeme('expAssign', r'\*\*='), 108 | Lexeme('modAssign', r'%='), 109 | Lexeme('dAssign', r'/='), 110 | Lexeme('mAssign', r'\*='), 111 | Lexeme('sAssign', r'-='), 112 | Lexeme('pAssign', r'\+='), 113 | Lexeme('andAssign', r'\&='), 114 | Lexeme('orAssign', r'\|='), 115 | Lexeme('xorAssign', r'\^='), 116 | Lexeme('lShift', r'<<'), 117 | Lexeme('rShift', r'>>'), 118 | Lexeme('exponent', r'\*\*'), 119 | Lexeme('assign', r'='), 120 | Lexeme('comma', r','), 121 | Lexeme('lParen', r'\('), 122 | Lexeme('rParen', r'\)'), 123 | Lexeme('factorial', r'!'), 124 | Lexeme('modulo', r'%'), 125 | Lexeme('divide', r'/'), 126 | Lexeme('multiply', r'\*'), 127 | Lexeme('subtract', r'-'), 128 | Lexeme('plus', r'\+'), 129 | Lexeme('and', r'\&'), 130 | Lexeme('or', r'\|'), 131 | Lexeme('xor', r'\^'), 132 | Lexeme('decDir', r':dec'), 133 | Lexeme('hexDir', r':hex'), 134 | Lexeme('octDir', r':oct'), 135 | Lexeme('statusDir', r':status'), 136 | Lexeme('statusDir', r':s'), #shorthand 137 | Lexeme('varDir', r':vars'), 138 | Lexeme('quitDir', r':q'), 139 | Lexeme('intDir', r':int'), 140 | Lexeme('floatDir', r':float') ] 141 | 142 | #takes an expression and uses the language lexemes 143 | #to produce a sequence of tokens 144 | def tokenize(expr): 145 | tokens = [] 146 | while expr != '': 147 | matchedLexeme = False 148 | for lexeme in lexemes: 149 | match = matchesFront(lexeme.regex, expr) 150 | if match != '': 151 | tokens.append(Token(lexeme.ID, match)) 152 | expr = expr[len(match):] 153 | matchedLexeme = True 154 | break 155 | if not matchedLexeme: return [Token('ERROR', expr)] 156 | return filter(lambda t: t.ID != 'whitespace', tokens) 157 | 158 | #returns the match if regex matches beginning of string 159 | #otherwise returns the emtpy string 160 | def matchesFront(regex, string): 161 | rexp = re.compile(regex) 162 | m = rexp.match(string) 163 | if m: 164 | return m.group() 165 | else: 166 | return '' 167 | 168 | #useful for testing tokenize with map(...) 169 | def getAttrib(token): 170 | return token.attrib 171 | 172 | def getID(token): 173 | return token.ID 174 | 175 | #### PARSER FUNCTIONS ############################################################ 176 | 177 | #TODO: this is all a bit messy due to passing essentially a vector around 178 | # instead of a list and not having shared state. Could be made a 179 | # lot simpler by using shared state... 180 | 181 | #vcalc context-free grammar 182 | #line -> directive | expr | assign 183 | #directive -> decDir | octDir | hexDir | intDir | floatDir | statusDir | varDir | quitDir 184 | #assign -> let assign' | assign' 185 | #assign' -> ident = expr | ident += expr | ident -= expr 186 | # | ident *= expr | ident /= expr | ident %= expr | ident **= expr 187 | #expr -> expr + term | expr - term | term 188 | #func -> ident ( args ) 189 | #args -> expr , args | expr 190 | #term -> term * factor | term / factor | term % factor 191 | # | term << factor | term >> factor | term ! | factor 192 | #factor -> expt ** factor | expt 193 | #expt -> func | ident | - number | number | ( expr ) 194 | #number -> decnumber | hexnumber | octalnumber 195 | 196 | #vcalc context-free grammar LL(1) -- to be used with a recursive descent parser 197 | #line -> directive | assign | expr 198 | #directive -> decDir | octDir | hexDir | intDir | floatDir | statusDir | varDir | quitDir 199 | #assign -> [let] ident (=|+=|-=|*=|/=|%=|**=) expr 200 | #expr -> term {(+|-) term} 201 | #func -> ident ( args ) 202 | #args -> expr {, expr} 203 | #term -> factor {(*|/|%|<<|>>) factor} [!] 204 | #factor -> {expt **} expt 205 | #expt -> func | ident | - number | number | ( expr ) 206 | #number -> decnumber | hexnumber | octalnumber 207 | 208 | class ParseNode(object): 209 | def __init__(self, success, result, consumedTokens): 210 | self._success = success 211 | self._result = result 212 | self._consumedTokens = consumedTokens 213 | self._storeInAns = True 214 | self._assignedSymbol = '' 215 | def getSuccess(self): 216 | return self._success 217 | def getResult(self): 218 | return self._result 219 | def getConsumed(self): 220 | return self._consumedTokens 221 | def getStoreInAns(self): 222 | return self._storeInAns 223 | def setStoreInAns(self, val): 224 | self._storeInAns = val 225 | def getAssignedSymbol(self): 226 | return self._assignedSymbol 227 | def setAssignedSymbol(self, val): 228 | self._assignedSymbol = val 229 | success = property(getSuccess, 230 | doc='Successfully evaluated?') 231 | result = property(getResult, 232 | doc='The evaluated result at this node.') 233 | consumeCount = property(getConsumed, 234 | doc='Number of consumed tokens.') 235 | storeInAns = property(getStoreInAns, setStoreInAns, 236 | doc='Should store in ans variable?') 237 | assignedSymbol = property(getAssignedSymbol, setAssignedSymbol, 238 | doc='Symbol expression assigned to.') 239 | 240 | class ParseException(Exception): 241 | def __init__(self, message, consumedTokens): 242 | self._message = message 243 | self._consumed = consumedTokens 244 | def getMessage(self): 245 | return self._message 246 | def getConsumed(self): 247 | return self._consumed 248 | message = property(getMessage) 249 | consumed = property(getConsumed) 250 | 251 | #recursive descent parser -- simple and befitting the needs of this small program 252 | #generates the parse tree with evaluated decoration 253 | def parse(expr): 254 | tokens = tokenize(expr) 255 | if symbolCheck('ERROR', 0, tokens): 256 | return 'Syntax error: ' + tokens[0].attrib 257 | try: 258 | lineNode = line(tokens) 259 | if lineNode.success: 260 | if lineNode.storeInAns: 261 | storeSymbol('ans', lineNode.result) 262 | return 'ans = ' + process(lineNode.result) 263 | else: 264 | if lineNode.assignedSymbol == None: 265 | return str(lineNode.result) 266 | else: 267 | return lineNode.assignedSymbol + ' = ' + process(lineNode.result) 268 | else: 269 | return 'Parse error: the expression is invalid.' 270 | except ParseException, pe: 271 | return 'Parse error: ' + pe.message 272 | 273 | #this function returns an output string based on the global repl directives 274 | def process(result): 275 | if VCALC_OUTPUT_BASE == 'decimal': 276 | output = result 277 | elif VCALC_OUTPUT_BASE == 'hexadecimal': 278 | return str(hex(int(result))) 279 | elif VCALC_OUTPUT_BASE == 'octal': 280 | return str(oct(int(result))) 281 | else: 282 | return 'ERROR' 283 | 284 | if VCALC_OUTPUT_PRECISION == 'int': 285 | return str(int(output)) 286 | elif VCALC_OUTPUT_PRECISION == 'float': 287 | return str(output) 288 | else: 289 | return 'ERROR' 290 | 291 | 292 | def line(tokens): 293 | directiveNode = directive(tokens) 294 | if directiveNode.success: 295 | if directiveNode.consumeCount == len(tokens): 296 | return directiveNode 297 | else: 298 | return ParseNode(False, 0, directiveNode.consumeCount) 299 | assignNode = assign(tokens) 300 | if assignNode.success: 301 | if assignNode.consumeCount == len(tokens): 302 | return assignNode 303 | else: 304 | return ParseNode(False, 0, assignNode.consumeCount) 305 | exprNode = expr(tokens) 306 | if exprNode.success: 307 | if exprNode.consumeCount == len(tokens): 308 | return exprNode 309 | else: 310 | return ParseNode(False, 0, exprNode.consumeCount) 311 | return ParseNode(False, 0, 0) 312 | 313 | VCALC_OUTPUT_BASE = 'decimal' 314 | VCALC_OUTPUT_PRECISION = 'float' 315 | 316 | def directive(tokens): 317 | #TODO: refactor this -- extract method 318 | global VCALC_OUTPUT_BASE 319 | global VCALC_OUTPUT_PRECISION 320 | if symbolCheck('decDir', 0, tokens): 321 | VCALC_OUTPUT_BASE = 'decimal' 322 | return createDirectiveParseNode('CHANGED OUTPUT BASE TO DECIMAL.') 323 | if symbolCheck('hexDir', 0, tokens): 324 | VCALC_OUTPUT_BASE = 'hexadecimal' 325 | return createDirectiveParseNode('CHANGED OUTPUT BASE TO HEXADECIMAL.') 326 | if symbolCheck('octDir', 0, tokens): 327 | VCALC_OUTPUT_BASE = 'octal' 328 | return createDirectiveParseNode('CHANGED OUTPUT BASE TO OCTAL.') 329 | if symbolCheck('floatDir', 0, tokens): 330 | VCALC_OUTPUT_PRECISION = 'float' 331 | return createDirectiveParseNode('CHANGED OUTPUT PRECISION TO FLOATING POINT.') 332 | if symbolCheck('intDir', 0, tokens): 333 | VCALC_OUTPUT_PRECISION = 'int' 334 | return createDirectiveParseNode('CHANGED OUTPUT PRECISION TO INTEGER.') 335 | if symbolCheck('statusDir', 0, tokens): 336 | return createDirectiveParseNode(statusMessage()) 337 | if symbolCheck('varDir', 0, tokens): 338 | return createDirectiveParseNode(variablesMessage()) 339 | if symbolCheck('quitDir', 0, tokens): 340 | return createDirectiveParseNode('!!!q!!!') 341 | return ParseNode(False, 0, 0) 342 | 343 | def assign(tokens): 344 | if symbolCheck('ident', 0, tokens): 345 | assignPos = 1 346 | elif map(getID, tokens[0:2]) == ['let', 'ident']: 347 | assignPos = 2 348 | else: 349 | return ParseNode(False, 0, 0) 350 | 351 | exprNode = expr(tokens[assignPos+1:]) 352 | if exprNode.consumeCount+assignPos+1 == len(tokens): 353 | symbol = tokens[assignPos-1].attrib 354 | 355 | #perform type of assignment 356 | if symbolCheck('assign', assignPos, tokens): 357 | result = exprNode.result 358 | else: 359 | result = lookupSymbol(symbol) 360 | if symbolCheck('pAssign', assignPos, tokens): 361 | result = result + exprNode.result 362 | elif symbolCheck('sAssign', assignPos, tokens): 363 | result = result - exprNode.result 364 | elif symbolCheck('mAssign', assignPos, tokens): 365 | result = result * exprNode.result 366 | elif symbolCheck('dAssign', assignPos, tokens): 367 | result = result / exprNode.result 368 | elif symbolCheck('modAssign', assignPos, tokens): 369 | result = result % exprNode.result 370 | 371 | #arguments to bitwise operations must be plain or long integers 372 | elif symbolCheck('andAssign', assignPos, tokens): 373 | result = int(result) & int(exprNode.result) 374 | elif symbolCheck('orAssign', assignPos, tokens): 375 | result = int(result) | int(exprNode.result) 376 | elif symbolCheck('xorAssign', assignPos, tokens): 377 | result = int(result) ^ int(exprNode.result) 378 | 379 | elif symbolCheck('expAssign', assignPos, tokens): 380 | result = result ** exprNode.result 381 | else: 382 | return ParseNode(False,0,assignPos) 383 | 384 | storeSymbol(symbol, result) 385 | node = ParseNode(True, result, exprNode.consumeCount+assignPos+1) 386 | node.storeInAns = False 387 | node.assignedSymbol = symbol 388 | return node 389 | else: 390 | return ParseNode(False, 0, exprNode.consumeCount+assignPos+1) 391 | 392 | def expr(tokens): 393 | termNode = term(tokens) 394 | consumed = termNode.consumeCount 395 | if termNode.success: 396 | foldNode = foldlParseMult(term, 397 | [lambda x,y:x+y, lambda x,y:x-y], 398 | ['plus','subtract'], 399 | termNode.result, 400 | tokens[consumed:]) 401 | consumed += foldNode.consumeCount 402 | return ParseNode(foldNode.success, foldNode.result, consumed) 403 | else: 404 | return ParseNode(False, 0, consumed) 405 | 406 | def func(tokens): 407 | if map(getID, tokens[0:2]) == ['ident', 'lParen']: 408 | sym = tokens[0].attrib 409 | argsNode = args(tokens[2:]) 410 | if symbolCheck('rParen', argsNode.consumeCount+2, tokens): 411 | try: 412 | result = apply(lookupFunc(sym), argsNode.result) 413 | return ParseNode(True, result, argsNode.consumeCount+3) 414 | except TypeError, e: 415 | raise ParseException, (str(e), argsNode.consumeCount+3) 416 | except ValueError, e: 417 | raise ParseException, (str(e), argsNode.consumeCount+3) 418 | else: 419 | error = 'missing matching parenthesis for function ' + sym + '.' 420 | raise ParseException, (error, argsNode.consumeCount+2) 421 | else: 422 | return ParseNode(False, 0, 0) 423 | 424 | def args(tokens): 425 | #returns a list of exprNodes to be used as function arguments 426 | exprNode = expr(tokens) 427 | consumed = exprNode.consumeCount 428 | if exprNode.success: 429 | foldNode = foldlParse(expr, snoc, 'comma', [exprNode.result], tokens[consumed:]) 430 | return ParseNode(foldNode.success, foldNode.result, consumed+foldNode.consumeCount) 431 | else: 432 | return ParseNode(False, [], consumed) 433 | 434 | def term(tokens): 435 | factNode = factor(tokens) 436 | consumed = factNode.consumeCount 437 | if factNode.success: 438 | foldNode = foldlParseMult(factor, 439 | [lambda x,y:x*y, lambda x,y:x/y, lambda x,y:x%y, 440 | lambda x,y:int(x)&int(y), 441 | lambda x,y:int(x)|int(y), 442 | lambda x,y:int(x)^int(y), 443 | lambda x,y:int(x)<>int(y)], 444 | ['multiply', 'divide', 'modulo', 'and', 'or', 445 | 'xor', 'lShift', 'rShift'], 446 | factNode.result, 447 | tokens[consumed:]) 448 | consumed += foldNode.consumeCount 449 | if symbolCheck('factorial', consumed, tokens): 450 | return ParseNode(foldNode.success, factorial(foldNode.result), consumed+1) 451 | else: 452 | return ParseNode(foldNode.success, foldNode.result, consumed) 453 | else: 454 | return ParseNode(False, 0, consumed) 455 | 456 | def factor(tokens): 457 | exptNode = expt(tokens) 458 | consumed = exptNode.consumeCount 459 | result = exptNode.result 460 | if exptNode.success: 461 | foldNode = foldrParse(expt, lambda x,y:x**y, 'exponent', result, tokens[consumed:]) 462 | return ParseNode(foldNode.success, foldNode.result, consumed+foldNode.consumeCount) 463 | else: 464 | return ParseNode(False, 0, consumed) 465 | 466 | def expt(tokens): 467 | #function 468 | funcNode = func(tokens) 469 | if funcNode.success: 470 | return funcNode 471 | #identifier 472 | if symbolCheck('ident', 0, tokens): 473 | return ParseNode(True, lookupSymbol(tokens[0].attrib), 1) 474 | #unary - 475 | if symbolCheck('subtract', 0, tokens): 476 | numberNode = number(tokens[1:]) 477 | if numberNode.success: 478 | return ParseNode(True, numberNode.result*-1, numberNode.consumeCount+1) 479 | #plain number 480 | numberNode = number(tokens) 481 | if numberNode.success: 482 | return numberNode 483 | #(expr) 484 | if symbolCheck('lParen', 0, tokens): 485 | exprNode = expr(tokens[1:]) 486 | if exprNode.success: 487 | if symbolCheck('rParen', exprNode.consumeCount+1, tokens): 488 | return ParseNode(True, exprNode.result, exprNode.consumeCount+2) 489 | else: 490 | error = 'missing matching parenthesis in expression.' 491 | raise ParseException, (error, exprNode.consumeCount+1) 492 | return ParseNode(False, 0, 0) 493 | 494 | def number(tokens): 495 | if symbolCheck('decnumber', 0, tokens): 496 | if VCALC_OUTPUT_PRECISION == 'float': 497 | num = float(tokens[0].attrib) 498 | elif VCALC_OUTPUT_PRECISION == 'int': 499 | num = int(float(tokens[0].attrib)) #int from float as string input 500 | else: 501 | num = 0 #error 502 | return ParseNode(True, num, 1) 503 | elif symbolCheck('hexnumber', 0, tokens): 504 | return ParseNode(True, long(tokens[0].attrib,16), 1) 505 | elif symbolCheck('octnumber', 0, tokens): 506 | return ParseNode(True, long(tokens[0].attrib,8), 1) 507 | else: 508 | return ParseNode(False, 0, 0) 509 | 510 | #### HELPER FUNCTIONS FOR USE BY THE PARSER AND REPL ############################# 511 | 512 | def foldlParse(parsefn, resfn, symbol, initial, tokens): 513 | consumed = 0 514 | result = initial 515 | if tokens == []: 516 | return ParseNode(True, result, consumed) 517 | else: 518 | while tokens[consumed].ID == symbol: 519 | parseNode = parsefn(tokens[consumed+1:]) 520 | consumed += parseNode.consumeCount+1 521 | if parseNode.success: 522 | result = resfn(result, parseNode.result) 523 | if consumed >= len(tokens): return ParseNode(True,result,consumed) 524 | else: 525 | return ParseNode(False, 0, consumed) 526 | return ParseNode(True, result, consumed) 527 | 528 | def foldlParseMult(parsefn, resfns, syms, initial, tokens): 529 | consumed = 0 530 | result = initial 531 | if tokens == []: 532 | return ParseNode(True, result, consumed) 533 | else: 534 | while tokens[consumed].ID in syms: 535 | sym = tokens[consumed].ID 536 | parseNode = foldlParse(parsefn, resfns[syms.index(sym)], sym, result, tokens[consumed:]) 537 | if parseNode.success: 538 | result = parseNode.result 539 | consumed += parseNode.consumeCount 540 | if consumed >= len(tokens): return ParseNode(True,result,consumed) 541 | else: 542 | return ParseNode(False, 0, consumed) 543 | return ParseNode(True, result, consumed) 544 | 545 | def foldrParse(parsefn, resfn, symbol, initial, tokens): 546 | #foldlParse into a sequence and then do a foldr to evaluate 547 | parseNode = foldlParse(parsefn, snoc, symbol, [], tokens) 548 | if parseNode.success: 549 | result = foldr(resfn, initial, parseNode.result) 550 | return ParseNode(parseNode.success, result, parseNode.consumeCount) 551 | else: 552 | return parseNode 553 | 554 | def createDirectiveParseNode(outputMsg): 555 | node = ParseNode(True, outputMsg, 1) 556 | node.storeInAns = False 557 | node.assignedSymbol = None 558 | return node 559 | 560 | def statusMessage(): 561 | global VCALC_OUTPUT_BASE 562 | global VCALC_OUTPUT_PRECISION 563 | 564 | base = VCALC_OUTPUT_BASE.upper() 565 | if VCALC_OUTPUT_PRECISION == 'float' : precision = 'FLOATING POINT' 566 | elif VCALC_OUTPUT_PRECISION == 'int' : precision = 'INTEGER' 567 | else: VCALC_OUTPUT_PRECISION = 'ERROR' 568 | msg = "STATUS: OUTPUT BASE: %s; PRECISION: %s." % (base, precision) 569 | return msg 570 | 571 | def variablesMessage(): 572 | msg = "VARIABLES:\n----------\n" 573 | #find the longest variable length for alignment 574 | width = 0 575 | for k in VCALC_SYMBOL_TABLE.keys(): 576 | width = max(width, len(k)) 577 | 578 | items = VCALC_SYMBOL_TABLE.items() 579 | items.sort() 580 | for k,v in items: 581 | msg += " " + k.ljust(width) + " : " + process(v) + "\n" 582 | return msg 583 | 584 | #rather literal haskell implementation of this, proably very 585 | #unpythonic and inefficient. Should do for the needs of vimcalc 586 | #however. TODO: in the future improve this! 587 | def foldr(fn, init, lst): 588 | if lst == []: 589 | return init 590 | else: 591 | return fn(init, foldr(fn, lst[0], lst[1:])) 592 | 593 | def symbolCheck(symbol, index, tokens): 594 | if index < len(tokens): 595 | if tokens[index].ID == symbol: 596 | return True 597 | return False 598 | 599 | def snoc(seq, x): #TODO: find more pythonic way of doing this 600 | a = seq 601 | a.append(x) 602 | return a 603 | 604 | #### SYMBOL TABLE MANIPULATION FUNCTIONS ######################################### 605 | 606 | #global symbol table NOTE: these can be rebound 607 | VCALC_SYMBOL_TABLE = {'ans':0, 608 | 'e':math.e, 609 | 'pi':math.pi, 610 | 'phi':1.6180339887498948482} 611 | 612 | def lookupSymbol(symbol): 613 | if VCALC_SYMBOL_TABLE.has_key(symbol): 614 | return VCALC_SYMBOL_TABLE[symbol] 615 | else: 616 | error = "symbol '" + symbol + "' is not defined." 617 | raise ParseException, (error, 0) 618 | 619 | def storeSymbol(symbol, value): 620 | VCALC_SYMBOL_TABLE[symbol] = value 621 | 622 | 623 | #### VIMCALC BUILTIN FUNCTIONS ################################################### 624 | 625 | def loge(n): 626 | return math.log(n) 627 | 628 | def log2(n): 629 | return math.log(n, 2) 630 | 631 | def nrt(x,y): 632 | return x**(1/y) 633 | 634 | def factorial(n): 635 | acc = 1 636 | for i in xrange(int(n)): 637 | acc *= i+1 638 | return acc 639 | 640 | def perms(n,k): 641 | return factorial(n)/factorial(n-k) 642 | 643 | def choose(n,k): 644 | denominator = factorial(k) * factorial(n-k) 645 | return factorial(n)/denominator 646 | 647 | #global built-in function table 648 | #NOTE: variables do not share the same namespace as functions 649 | #NOTE: if you change the name or add a function remember to update the syntax file 650 | 651 | VCALC_FUNCTION_TABLE = { 652 | 'abs' : math.fabs, 653 | 'acos' : math.acos, 654 | 'asin' : math.asin, 655 | 'atan' : math.atan, 656 | 'atan2' : math.atan2, 657 | 'ceil' : math.ceil, 658 | 'choose': choose, 659 | 'cos' : math.cos, 660 | 'cosh' : math.cosh, 661 | 'deg' : math.degrees, 662 | 'exp' : math.exp, 663 | 'floor' : math.floor, 664 | 'hypot' : math.hypot, 665 | 'inv' : lambda n: 1/n, 666 | 'ldexp' : math.ldexp, 667 | 'lg' : log2, 668 | 'ln' : loge, 669 | 'log' : math.log, #allows arbitrary base, defaults to e 670 | 'log10' : math.log10, 671 | 'max' : max, 672 | 'min' : min, 673 | 'nrt' : nrt, 674 | 'perms' : perms, 675 | 'pow' : math.pow, 676 | 'rad' : math.radians, 677 | 'rand' : random.random, #random() -> x in the interval [0, 1). 678 | 'round' : round, 679 | 'sin' : math.sin, 680 | 'sinh' : math.sinh, 681 | 'sqrt' : math.sqrt, 682 | 'tan' : math.tan, 683 | 'tanh' : math.tanh 684 | } 685 | 686 | def lookupFunc(symbol): 687 | if VCALC_FUNCTION_TABLE.has_key(symbol): 688 | return VCALC_FUNCTION_TABLE[symbol] 689 | else: 690 | error = "built-in function '" + symbol + "' does not exist." 691 | raise ParseException, (error, 0) 692 | -------------------------------------------------------------------------------- /doc/vimcalc.txt: -------------------------------------------------------------------------------- 1 | *VimCalc* A plugin to run an interactive calculator inside a Vim buffer. 2 | 3 | Author: Greg Sexton *vimcalc-author* 4 | License: Same terms as Vim itself (see |license|). 5 | URL: https://github.com/gregsexton/VimCalc 6 | 7 | 8 | VimCalc *vimcalc* *:Calc* 9 | 10 | 1. Introduction |vimcalc-introduction| 11 | 2. Installation |vimcalc-installation| 12 | 3. Usage |vimcalc-usage| 13 | 4. Operators |vimcalc-operators| 14 | 5. Functions |vimcalc-functions| 15 | 6. Variables and Literals |vimcalc-vars-literals| 16 | 7. Configuration Options |vimcalc-config-options| 17 | 8. Changelog |vimcalc-changelog| 18 | 9. Misc |vimcalc-misc| 19 | 20 | ============================================================================== 21 | 1. Introduction *vimcalc-introduction* 22 | 23 | VimCalc provides a convenient way to access a powerful interactive calculator 24 | whilst inside a Vim session. Quickly rattle off a few sums to test an idea. 25 | Perform complex calculations using built-in functions to validate answers. 26 | Quickly and simply convert from octal to hex to decimal and back again. Setup 27 | a bunch of variables to be used in a complex expression. Change variables 28 | easily and then simply re-evaluate the expression. Whatever you may need a 29 | calculator for, VimCalc is up to the task. 30 | 31 | Not only can VimCalc calculate but it uses Vim for input and editing. Quickly 32 | and easily perform edits on previous calculations using the power of Vim and 33 | then re-evaluate them by simply hitting . Once you've got the answers yank 34 | them into several registers and paste with ease into other buffers! 35 | 36 | ============================================================================== 37 | 2. Installation *vimcalc-installation* 38 | 39 | 2.1 Requirements *vimcalc-requirements* 40 | 41 | * Vim 7.0+ with +python. 42 | * Python installed. 43 | 44 | If you're compiling Vim from source, be sure to use the --enable-pythoninterp 45 | option. Otherwise check your OS's package distro for a version of Vim with 46 | Python support. On OS X the best option is MacVim. VimCalc should work on 47 | Windows too, you will need to install the correct python dll for the version 48 | of Vim you are using. Please see the web for help with this. 49 | 50 | 2.2 Installation *vimcalc-install* 51 | 52 | Download the latest source from https://github.com/gregsexton/VimCalc. 53 | 54 | Extract (or copy) all of the files into your Vim runtime directory, ensuring 55 | that they are placed into the correct subdirectories. Then update your help 56 | tags file to enable VimCalc help. See |add-local-help| for details. 57 | 58 | ============================================================================== 59 | 3. Usage *vimcalc-usage* 60 | 61 | Type :Calc to open a new window containing the VimCalc buffer. This is the 62 | only command that VimCalc defines. The VimCalc buffer has been tailored to act 63 | as a Read Eval Print Loop (REPL). That is, any command read at the prompt will 64 | be evaluated and the result printed back to you interactively. 65 | 66 | :Calc will place the cursor, in insert mode, at a prompt ready to issue 67 | commands to the REPL. This window should be closed as with any other window 68 | by issuing a :q. Running :Calc in a tab with a VimCalc window already open 69 | will cause the cursor to jump to that window. 70 | 71 | A typical interaction: 72 | > 73 | > 5+4 74 | ans = 9.0 75 | < 76 | The '> ' is the prompt. This is very important to VimCalc. Lines in the buffer 77 | with this prefixing them are evaluable by pressing on them whilst in 78 | insert or normal mode. Any lines that are not prefixed by the prompt are 79 | ignored. That is, if you are pressing return and VimCalc is not doing anything 80 | it is probably because it is not recognising a correct prompt. This prevents 81 | you from accidentally trying to evaluate results of previous expressions. The 82 | prompt is configurable, see |vimcalc-prompt|. 83 | 84 | '5+4' is the expression that is evaluated. VimCalc supports many operators and 85 | functions that are explained later in this documentation. (See |vimcalc-operators|). 86 | 87 | All results are prefixed with something of the form: 'ans = '. This tells you 88 | which variable contains the result of the evaluation (if it were successfully 89 | evaluated). In this case the 'ans' variable contains the result '9.0'. All 90 | evaluations are assigned to a variable. If the user does not explicitly state 91 | the variable to be assigned with a let statement then the default variable 92 | 'ans' is used. Variables can be evaluated by using them in expressions. See 93 | |vimcalc-defining-variables| for more about variables. 94 | 95 | Here are some further examples to whet your appetite. 96 | > 97 | > let x = 4 98 | x = 4.0 99 | > 9 * sqrt(4) 100 | ans = 18.0 101 | > 9**2 - (sqrt(4)+2) 102 | ans = 77.0 103 | > 0xff + 2 104 | ans = 257.0 105 | < 106 | 3.1 REPL Key Bindings *vimcalc-key-bindings* 107 | 108 | The VimCalc buffer rebinds as few keys as necessary to preserve as much Vim 109 | functionality as possible. Searching, yanking, highlighting text etc. should 110 | work just fine. The idea is that you can use the buffer just like any other. 111 | In particular the ability to edit previous expressions should be possible. 112 | This is to allow for correcting for mistakes or adjustments to complicated 113 | equations. Be careful with editing though as re-evaluating the expression does 114 | not replace the previous answer and may look confusing when you come back to 115 | it. It is probably safer to use the history navigation. 116 | 117 | Command Mode Description ~ 118 | 119 | || Normal This will evaluate the expression on the line 120 | that the cursor currently is on. The result is 121 | displayed at the bottom of the buffer and the 122 | prompt is jumped to. 123 | 124 | || Insert This does the same thing as in normal mode but 125 | leaves you in insert mode at the prompt. 126 | 127 | |o| Normal This will jump to the prompt and place you in 128 | insert mode from anywhere in the buffer. 129 | 130 | |O| Normal The same as normal mode o. 131 | 132 | || Insert This recalls the previous expression that was 133 | evaluated. This allows you to browse through 134 | the history of the REPL. See |vimcalc-history-size| 135 | to configure how many items of history are kept. 136 | 137 | || Insert This recalls the next expression that was 138 | evaluated. Opposite in effect to . 139 | 140 | || Normal This opens this help page at the 141 | function-list. Useful for quickly looking up a 142 | forgotten function. 143 | 144 | 145 | 3.2 Directives *vimcalc-directives* 146 | 147 | Directives are a method of issuing commands to the actual REPL environment. 148 | All directives start with a ':'. Directives have a global effect on how the 149 | REPL works and take effect immediately. The currently recognised directives 150 | are: 151 | 152 | Directive Effect~ 153 | 154 | |:dec| Puts the REPL into decimal mode. All results are evaluated and 155 | then converted into decimal before being displayed. This is 156 | the default. 157 | 158 | |:hex| Puts the REPL into hexadecimal mode. All results are evaluated 159 | and then converted into hexadecimal before being displayed. 160 | 161 | |:oct| Puts the REPL into octal mode. All results are evaluated and 162 | then converted into octal before being displayed. 163 | 164 | |:int| Puts the environment into integer mode. All results are 165 | evaluated using integer precision arithmetic. 166 | 167 | |:float| Puts the environment into floating point mode. All results 168 | are evaluated using floating point precision arithmetic. This 169 | is the default. 170 | 171 | |:s[tatus]| This has no effect on the environment. It displays a status 172 | message informing as to the state of the environment. 173 | 174 | |:vars| This has no effect on the environment. It displays a list of 175 | all of the currently bound variables and their assigned value. 176 | 177 | |:q| This allows you to quit and close the VimCalc window exactly 178 | like performing ':q' in normal mode. This is convenient if you 179 | have the |vimcalc-insert-on-enter| option enabled. 180 | 181 | The :dec, :hex and :oct directives are particularly useful for converting 182 | between the different number bases. Numbers are still evaluated by the REPL 183 | based on how they are entered, i.e the number '10' is always the decimal 184 | number 10 no matter what mode the REPL is in. If you meant hexadecimal '10' 185 | then you must specify '0x10'. See |vimcalc-literals| for how to specify the 186 | different number literals. This allows for easy conversion: 187 | > 188 | > :hex 189 | CHANGED OUTPUT BASE TO HEXADECIMAL. 190 | > 10 191 | ans = 0xa 192 | > 0x10 193 | ans = 0x10 194 | > :dec 195 | CHANGED OUTPUT BASE TO DECIMAL. 196 | > 0x10 197 | ans = 16 198 | < 199 | The :int and :float directives only have an effect whilst the REPL is in 200 | decimal mode. When in octal or hexadecimal modes all number literals are 201 | calculated using integer precision regardless. It is not possible to enter 202 | fractional octal or hexadecimal literals. Decimal floating point precision is 203 | the default mode the REPL starts in. 204 | 205 | When in integer mode all expressions are evaluated using integers, this 206 | includes sub-expressions. Results are not evaluated and then lastly converted 207 | to an integer. For example: 208 | > 209 | > :float 210 | CHANGED OUTPUT PRECISION TO FLOATING POINT. 211 | > (8/3) * (4/3) 212 | ans = 3.55555555556 213 | > :int 214 | CHANGED OUTPUT PRECISION TO INTEGER. 215 | > (8/3) * (4/3) 216 | ans = 2 217 | < 218 | The integer mode answer to this expression was 2 rather than 3 as the 219 | sub-expressions (8/3) and (4/3) were calculated also in integer mode. 220 | 221 | The :status directive gives a brief summary of the current state of the REPL 222 | environment. It can be shortened to ":s". This is an example output. 223 | > 224 | > :status 225 | STATUS: OUTPUT BASE: DECIMAL; PRECISION: FLOATING POINT. 226 | < 227 | 228 | The :vars directive lists all of the currently bound variables and their 229 | assigned values. Here is a sample usage. 230 | > 231 | > let x = 3.0 232 | x = 3.0 233 | > :vars 234 | VARIABLES: 235 | ---------- 236 | ans : 4.0 237 | e : 2.71828182846 238 | phi : 1.61803398875 239 | pi : 3.14159265359 240 | x : 3.0 241 | < 242 | 243 | ============================================================================== 244 | 4. Operators *vimcalc-operators* 245 | 246 | There are several operators you can use in VimCalc. They are listed here by 247 | precedence i.e. the operators closest to the top of this table are evaluated 248 | first. Also detailed is the associativity of the operator, whether it is 249 | left-to-right or right-to-left. This determines the order of evaluation of 250 | multiple uses of this operator. For example: 251 | > 252 | > 2**2**3 253 | ans = 256.0 254 | > 2**(2**3) 255 | ans = 256.0 256 | > (2**2)**3 257 | ans = 64.0 258 | < 259 | The '**' operator uses right-to-left associativity. Oppositely, the '/' 260 | operator uses left-to-right associativity: 261 | > 262 | > 2/2/3 263 | ans = 0.333333333333 264 | > (2/2)/3 265 | ans = 0.333333333333 266 | > 2/(2/3) 267 | ans = 3.0 268 | < 269 | VimCalc supports wrapping expressions in parenthesis to evaluate them out of 270 | turn. This is done with left-to-right associativity. 271 | 272 | Precedence Operator Description Associativity~ 273 | 274 | 0 () Function application. Left-to-right 275 | 276 | 1 - Unary minus sign. Left-to-right 277 | 278 | 2 ** Exponentiation. Right-to-left 279 | 280 | 3 * / Multiplication and division. Left-to-right 281 | 3 % Modulus Left-to-right 282 | 3 << >> Arithmetic left and right shift. Left-to-right 283 | 3 ! Factorial (bang). Left-to-right 284 | 3 & | ^ Bitwise (and, or, xor) Left-to-right 285 | 286 | 4 + - Addition and subtraction. Left-to-right 287 | 288 | 5 = Assignment Right-to-left 289 | 5 += -= Assignment by sum and difference. Right-to-left 290 | 5 *= /= Assignment by product and division. Right-to-left 291 | 5 %= Assignment by remainder (modulus). Right-to-left 292 | 5 **= Assignment by exponent. Right-to-left 293 | 5 &= |= ^= Assignment by bitwise operation. Right-to-left 294 | 295 | ============================================================================== 296 | 5. Functions *vimcalc-functions* 297 | 298 | VimCalc is supplied with many built-in functions. These are detailed here 299 | including their expected outputs. 300 | 301 | It is not possible, and never will be, to define your own functions in 302 | VimCalc. This is a design decision of the plugin. VimCalc is an expression 303 | evaluator and NOT a programming language. This is for two reasons. Firstly, 304 | VimCalc intends to remain simple and straightforward to use -- so that you 305 | will use it! Secondly, in order to add functions it would be necessary to add 306 | conditionals (if-then-else etc.) and loops (via recursion or iteration). I do 307 | not wish to add loops at any cost. It should be very difficult to crash a text 308 | editor whether purposely or not and adding loops negates this. 309 | 310 | If you find that VimCalc is missing functions that you would like to use 311 | please submit requests to me (see |vimcalc-feedback|). Alternatively it is 312 | straightforward to add functions to the plugin. It is a matter of defining the 313 | function (in Python) and then adding it to the function symbol table. It 314 | should be fairly obvious how this works upon examining the source. If you do 315 | add functions that you think others would benefit from please, please send me 316 | patches. 317 | 318 | *vimcalc-function-list* 319 | Function Description~ 320 | abs(x) Returns the absolute (unsigned) value of x. 321 | 322 | acos(x) Returns the arc cosine of x (in radians). 323 | 324 | asin(x) Returns the arc sine (measured in radians) of x. 325 | 326 | atan(x) Returns the arc tangent (measured in radians) of x. 327 | 328 | atan2(y,x) Returns the arc tangent (measured in radians) of y/x. 329 | Unlike atan(y/x), the signs of both x and y are considered. 330 | 331 | ceil(x) Returns the smallest integral value >= x. 332 | 333 | choose(n,k) Returns the binomail coefficient n `choose` k. 334 | 335 | cos(x) Returns the cosine of x (measured in radians). 336 | 337 | cosh(x) Returns the hyperbolic cosine of x. 338 | 339 | deg(x) Returns the degrees of angle x converted from radians. 340 | 341 | exp(x) Returns e raised to the power of x. 342 | 343 | floor(x) Returns the largest integral value <= x. 344 | 345 | hypot(x,y) Returns the Euclidean distance of x and y 346 | i.e sqrt(x*x + y*y) 347 | 348 | inv(x) Returns the inverse of x, i.e. (1/x). 349 | 350 | ldexp(x,i) Returns x * (2**i) 351 | 352 | lg(x) Returns the logarithm of x, base 2. 353 | 354 | ln(x) Returns the logarithm of x, base e. 355 | 356 | log(x,b) Returns the logarithm of x, base b. If b is not specified 357 | it defaults to base e. 358 | 359 | log10(x) Returns the logarithm of x, base 10. 360 | 361 | max(x,y) Returns the larger value of x and y. 362 | 363 | min(x,y) Returns the smaller value of x and y. 364 | 365 | nrt(x,n) Returns the nth root of x. 366 | 367 | perms(n,k) Returns the number of k-permutations of an n-set. 368 | 369 | pow(x,y) Returns x raised to the power of y (x**y). 370 | 371 | rad(x) Returns the radians of angle x converted from degrees. 372 | 373 | rand() Returns a random decimal number in the interval [0,1). 374 | 375 | round(x) Returns the nearest integral to x. 376 | 377 | sin(x) Returns the sine of x (measured in radians). 378 | 379 | sinh(x) Returns the hyperbolic sine of x (measured in radians). 380 | 381 | sqrt(x) Returns the square root of x. 382 | 383 | tan(x) Returns the tangent of x. 384 | 385 | tanh(x) Returns the hyperbolic tangent of x. 386 | 387 | ============================================================================== 388 | 6. Variables and Literals *vimcalc-vars-literals* 389 | 390 | It is possible in VimCalc to define variables for use in evaluating 391 | expressions. This can be very convenient when the expression is complicated. A 392 | straightforward example is the quadratic formula. 393 | > 394 | > let a = 2 395 | a = 2.0 396 | > let b = -1 397 | b = -1.0 398 | > let c = -6 399 | c = -6.0 400 | > ((b*-1) + sqrt(b**2 - 4*a*c))/(2*a) 401 | ans = 2.0 402 | > ((b*-1) - sqrt(b**2 - 4*a*c))/(2*a) 403 | ans = -1.5 404 | < 405 | As you can see from this example it is simple to define variables and simpler 406 | still to use them within expressions. It would now be possible to quickly 407 | redefine these variables and re-evaluate the two expressions to find the roots 408 | of another quadratic equation. 409 | 410 | Hint: To change the '+' for a '-' in the second equation it 411 | was possible to press the key to go back to the expression. Leave insert 412 | mode and run a search and replace to change it. Hitting evaluated the new 413 | expression. 414 | 415 | 6.1 Defining Variables *vimcalc-defining-variables* 416 | 417 | To define a variable the syntax is of the form 418 | > 419 | let identifier = expression 420 | < 421 | An identifier can begin with any of 'A-Z', 'a-z' or '_', it is then made up of 422 | any 'A-Z', 'a-z', '0-9' or '_'. Finally it may end in a single "'". Any legal 423 | expression can be used and the result of which will be bound to the 424 | identifier. 425 | 426 | Note: The 'let' in all assignments is optional and can be omitted for brevity. 427 | 428 | It is not necessary to declare a variable before assigning it and the type is 429 | always internally defined as a float. Arbitrary precision evaluation is a high 430 | priority upcoming feature of VimCalc. Redefining a variable is possible and is 431 | done by specifying it again in a new assignment. 432 | 433 | Note: To get a listing of all currently assigned variables the :vars directive 434 | can be used, see |vimcalc-directives|. 435 | 436 | 6.2 Variable Scope *vimcalc-variable-scope* 437 | 438 | VimCalc uses a single global scope for all variables. That is, all variables 439 | defined are usable at any point for any evaluation. A variable is bound to a 440 | value until it is redefined. 441 | 442 | Variables and functions do not share the same namespace. It is perfectly legal 443 | to define a variable with the same name as an existing function. This should 444 | cause no ambiguity with the grammar of VimCalc. 445 | 446 | 6.3 Assignment operators. *vimcalc-assignment-ops* 447 | 448 | Apart from the straightforward assignment operator '=', that assigns the result 449 | of an expression to a variable, there are 6 others. These manipulate the 450 | current value of the variable and result in an error if the variable is not 451 | already defined. 452 | 453 | Operator Equivalent to~ 454 | 455 | let x += y let x = x + y 456 | let x -= y let x = x - y 457 | let x *= y let x = x * y 458 | let x /= y let x = x / y 459 | let x %= y let x = x % y 460 | let x **= y let x = x ** y 461 | let x &= 7 let x = x & y 462 | let x |= 7 let x = x | y 463 | let x ^= 7 let x = x ^ y 464 | 465 | 6.4 Built-in Variables *vimcalc-builtin-variables* 466 | 467 | There are four built-in variables in VimCalc, one of which you will have 468 | already seen, 'ans'. 469 | 470 | These are: 471 | 472 | e - Euler's constant. 473 | pi - Ratio of any circle's circumference to its diameter in Euclidean space. 474 | phi - Golden ratio. Useful when working with Fibonacci sequences. 475 | ans - The last anonymous answer. 476 | 477 | The value of 'ans' changes as you evaluate expressions. 'ans' will always 478 | contain the result of the last expression you evaluated that you did not 479 | explicitly assign to a variable. Of course you can assign to 'ans' although 480 | this is rather pointless. 481 | 482 | Note also that it is possible to reassign 'e', 'pi' and 'phi'. You may do 483 | this if you wish but it is recommended that you do not. 484 | 485 | 6.5 Literals *vimcalc-literals* 486 | 487 | The only literals within the VimCalc 'language' are number literals. 488 | 489 | There are three number bases in VimCalc: decimal, hexadecimal and octal. It is 490 | possible to have VimCalc evaluate expressions and emit the answer using any of 491 | these bases, see |vimcalc-directives|. To specify any of these bases as 492 | literals use the following forms. 493 | 494 | Base Example Description~ 495 | Decimal 14.2 A 'regular' number. May specify a fractional part. 496 | 497 | Hexadecimal 0x1f A number prefixed with '0x'. The extra digits are 498 | made up of the letters A-F. Note this is case- 499 | insensitive. May only be an integer. 500 | 501 | Octal 042 A number prefixed with a '0'. May only use the 502 | digits 0-7. May only be an integer. 503 | 504 | ============================================================================== 505 | 7. Configuration Options *vimcalc-config-options* 506 | 507 | You can set the following options in your .vimrc to override the values used 508 | by VimCalc. The defaults are shown. 509 | 510 | 7.1 Buffer Title *vimcalc-buffer-title* 511 | 512 | The title used to name the VimCalc buffer. This shouldn't clash with another 513 | buffer name and is used to identify if a buffer is already open. 514 | > 515 | let g:VCalc_Title = "__VCALC__" 516 | < 517 | 7.2 Prompt *vimcalc-prompt* 518 | 519 | The prompt used by VimCalc. This is used to identify evaluable lines. 520 | > 521 | let g:VCalc_Prompt = "> " 522 | < 523 | 524 | 7.3 Window Size *vimcalc-win-size* 525 | 526 | The size of the window in lines (or columns if postion is vertical). 527 | > 528 | let g:VCalc_Win_Size = 10 529 | < 530 | 531 | 7.4 Number of History Items *vimcalc-history-size* 532 | 533 | The number of previous expressions that VimCalc remembers. These can be cycled 534 | using the up and down arrow keys. Reduce this value to reduce memory usage. 535 | > 536 | let g:VCalc_Max_History = 100 537 | < 538 | 7.5 Insert Mode when Entering Buffer *vimcalc-insert-on-enter* 539 | 540 | If set to 1, when entering the buffer, insert mode will automatically be 541 | activated and the cursor will jump to the prompt. If 0 Vim will behave as 542 | normally and will enter the buffer in normal mode. To evaluate expressions you 543 | must manually enter insert mode. This option is to facilitate performing quick 544 | calculations and then jumping back to the window you are working in. 545 | > 546 | let g:VCalc_InsertOnEnter = 0 547 | < 548 | 7.6 Enable Commands in Insert Mode *vimcalc-cw-insert-mode* 549 | 550 | If set to 1 then you may issue select based window commands whilst in 551 | insert mode in the VimCalc buffer. If set to 0 works as normal in insert 552 | mode and deletes a word. This can irritatingly delete the prompt. The only 553 | commands defined for insert mode are movement based, i.e. h,j,k,l,b,t,w 554 | and W. This option is to facilitate performing quick calculations and then 555 | jumping back to the window you are working in. 556 | > 557 | let g:VCalc_CWInsert = 0 558 | < 559 | 7.7 Window Positioning *vimcalc-window-position* 560 | 561 | This variable controls the opening position of VimCalc. Possible options are: 562 | 'top', 'bottom', 'left' and 'right'. If anything else is used the default is 563 | to open on the bottom. 'top' and 'bottom' define horizontal splits and 'left' 564 | and 'right' are vertical. If you choose 'left' or 'right' you may wish to up 565 | the default window size. 566 | > 567 | let g:VCalc_WindowPosition = 'top' 568 | < 569 | 570 | ============================================================================== 571 | 8. Changelog *vimcalc-changelog* 572 | 573 | 1.3 Added :int and :float directives. 574 | Added :status directive and improved directive highlighting. 575 | Added :vars directive. 576 | Added :q directive. 577 | Made 'let' optional during assignment. 578 | Everything is now unit tested with python code seperated. 579 | 580 | 1.2 Fixed a bug where global variables used for customisation were 581 | overwritten when VimCalc was sourced. 582 | 583 | 1.1 Added extra configuration options to allow for initial window 584 | positioning. As well as for entering the buffer in insert mode and 585 | leaving the buffer using commands whilst in insert mode. 586 | 587 | 1.0 First release. I hope you enjoy VimCalc! 588 | 589 | ============================================================================== 590 | 9. Misc *vimcalc-misc* 591 | 592 | 593 | 9.1 Known bugs *vimcalc-bugs* 594 | 595 | There are no known bugs. Hopefully there are not too many unknown. Please see 596 | |vimcalc-contribute| to help. 597 | 598 | The following are known limitations: 599 | 600 | * No complex numbers. 601 | * No arbitrary precision. 602 | * No lists of numbers (geometric/arithmetic progressions, statistics 603 | operations). 604 | * No stack based calculation. 605 | * No window positioning. 606 | 607 | Hopefully, all of these features will be addressed in future releases of 608 | VimCalc. If you wish to develop any of these or anything else for VimCalc 609 | please see the next section. 610 | 611 | 9.2 Contribute *vimcalc-contribute* 612 | 613 | Contributing to VimCalc couldn't be easier. If you wish to do development work 614 | on the code base or documentation simply fork the git repository and submit a 615 | pull request to me. If you discover a bug or wish to submit a feature request 616 | add an issue to the github page. Anything else, feel free to email me: 617 | gregsexton@gmail.com. 618 | 619 | 9.3 Feedback *vimcalc-feedback* 620 | 621 | Bugs, suggestions and patches are all very welcome. If you find issues with 622 | VimCalc please add them to the issues page on the github project. 623 | 624 | Check out the latest from github at https://github.com/gregsexton/VimCalc 625 | 626 | vim:tw=78:ts=8:ft=help:norl: 627 | --------------------------------------------------------------------------------