├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── build-tests-file.js ├── parinfer.vim ├── perf.vim ├── tests.vim └── tests ├── README.md ├── indent-mode.json ├── paren-mode.json └── really_long_file /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs. 2 | # More information at http://EditorConfig.org 3 | 4 | # Do not check for any .editorconfig files above this directory 5 | root = true 6 | 7 | # All files 8 | [*] 9 | charset = utf-8 10 | end_of_line = lf 11 | indent_size = 2 12 | indent_style = space 13 | insert_final_newline = true 14 | trim_trailing_whitespace = true 15 | 16 | # 4 space indentation 17 | [*.vim] 18 | indent_style = space 19 | indent_size = 4 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore diff files 2 | *.diff 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | script: 2 | - node build-tests-file.js 3 | - vim -S tests.vim 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.0 - 2018-02-14 2 | * Performance improvements from [PR #9]. Thank you [@eraserhd]! 3 | 4 | [PR #9]:https://github.com/oakmac/parinfer-viml/pull/9 5 | [@eraserhd]:https://github.com/eraserhd 6 | 7 | 8 | ## 0.1.0 - 2016-02-08 - First Release 9 | * Initial release! [Parinfer] is now usable directly from Vimscript :) 10 | 11 | [Parinfer]:http://shaunlebron.github.io/parinfer/ 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2016, Chris Oakman 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Parinfer in Vimscript [![Build Status](https://travis-ci.org/oakmac/parinfer-viml.svg?branch=master)](https://travis-ci.org/oakmac/parinfer-viml) 2 | 3 | A [Parinfer] implementation in [Vimscript]. 4 | 5 | ## Project Status (Dec 2022) 6 | 7 | I do not plan on actively maintaining this project moving forward. [parinfer-lua] 8 | exists and is **much** faster than Vimscript. NeoVim users may wish to check out 9 | the [nvim-parinfer] plugin or [parinfer-rust]. 10 | 11 | [parinfer-lua]:https://github.com/oakmac/parinfer-lua 12 | [nvim-parinfer]:https://github.com/gpanders/nvim-parinfer 13 | [parinfer-rust]:https://github.com/eraserhd/parinfer-rust 14 | 15 | ## About 16 | 17 | Having a Parinfer implementation written in Vimscript allows Parinfer to reach 18 | vi-based editors easily. 19 | 20 | Please note that this project is solely for the library that implements the core 21 | Parinfer algorithm; it is not a vi extension that can be used for editing. 22 | 23 | This is basically a 1-to-1 port of [parinfer.js]. 24 | 25 | The `.json` files in the [tests] folder are copied directly from the [main 26 | Parinfer repo]. 27 | 28 | This is my first Vimscript project. There is likely lots of room for improvement 29 | in this implementation. PR's welcome :) 30 | 31 | ## Usage 32 | 33 | TODO: write this section 34 | 35 | ## Run Tests 36 | 37 | Install [node.js] 38 | 39 | ```sh 40 | # write the tests.vim file 41 | node build-tests-file.js 42 | 43 | # run tests in vim 44 | vim -S tests.vim 45 | ``` 46 | 47 | Run performance test: 48 | 49 | ```sh 50 | vim -S perf.vim 51 | ``` 52 | 53 | ## License 54 | 55 | [ISC License] 56 | 57 | [Parinfer]:https://shaunlebron.github.io/parinfer/ 58 | [Vimscript]:https://en.wikipedia.org/wiki/Vim_(text_editor)#Vim_script 59 | [parinfer.js]:https://github.com/shaunlebron/parinfer/blob/master/lib/parinfer.js 60 | [tests]:tests/ 61 | [main Parinfer repo]:https://github.com/shaunlebron/parinfer/tree/master/lib/test/cases 62 | [parinfer.js API]:https://github.com/shaunlebron/parinfer/tree/master/lib#api 63 | [node.js]:https://nodejs.org 64 | [ISC License]:LICENSE.md 65 | -------------------------------------------------------------------------------- /build-tests-file.js: -------------------------------------------------------------------------------- 1 | // This file builds "tests.vim", which runs the Parinfer tests using Vimscript. 2 | 3 | const fs = require('fs'); 4 | const indentModeTests = require('./tests/indent-mode.json'); 5 | const parenModeTests = require('./tests/paren-mode.json'); 6 | const nl = '\n'; 7 | const nl2 = nl + nl; 8 | const warningLine = '""!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + nl; 9 | const squigglyLine = '""~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' + nl; 10 | const testFile = 'tests.vim'; 11 | 12 | var output = 13 | warningLine + 14 | warningLine + 15 | '""' + nl + 16 | '"" NOTE: This file is automatically generated from build-tests-file.js' + nl + 17 | '"" Please do not edit it directly' + nl + 18 | '""' + nl + 19 | warningLine + 20 | warningLine + nl + 21 | 'source parinfer.vim' + nl + 22 | 'let s:anyErrorsFound = 0' + nl2 + 23 | squigglyLine + 24 | '"" Indent Mode Tests' + nl + 25 | squigglyLine + nl; 26 | 27 | indentModeTests.forEach(function(test) { 28 | output += writeTestCase(test, 'indent') + nl2; 29 | }); 30 | 31 | output += squigglyLine + 32 | '"" Paren Mode Tests' + nl + 33 | squigglyLine + nl; 34 | 35 | parenModeTests.forEach(function(test) { 36 | output += writeTestCase(test, 'paren') + nl2; 37 | }); 38 | 39 | output += squigglyLine + 40 | '"" Show success if there were no failures' + nl + 41 | squigglyLine + 42 | 'if ! s:anyErrorsFound' + nl + 43 | ' silent !echo "All tests passed"' + nl + 44 | ' quit' + nl + 45 | 'else' + nl + 46 | ' cquit' + nl + 47 | 'endif' + nl; 48 | 49 | fs.writeFileSync(testFile, output, {encoding: 'utf8'}); 50 | 51 | //------------------------------------------------------------------------------ 52 | // Functions 53 | //------------------------------------------------------------------------------ 54 | 55 | function writeTestCase(test, mode) { 56 | const indentModeFn = 'g:ParinferLib.IndentMode'; 57 | const parenModeFn = 'g:ParinferLib.ParenMode'; 58 | 59 | var mainFn = indentModeFn; 60 | var oppositeFn = parenModeFn; 61 | var modeStr = 'Indent Mode'; 62 | 63 | if (mode === 'paren') { 64 | mainFn = parenModeFn; 65 | oppositeFn = indentModeFn; 66 | modeStr = 'Paren Mode'; 67 | } 68 | 69 | const testId = test.in.fileLineNo; 70 | const inText = test.in.lines.join(nl); 71 | const expectedText = test.out.lines.join(nl); 72 | const options = buildOptions(test.in.cursor); 73 | 74 | var c = ''; 75 | c += '"" Test Id: ' + testId + nl; 76 | c += 'let s:result = ' + mainFn + '(' + escapeVimlString(inText) + ', ' + options + ')' + nl; 77 | c += 'let s:expectedText = ' + escapeVimlString(expectedText) + nl; 78 | c += 'let s:outText = s:result.text' + nl; 79 | c += 'let s:result2 = ' + mainFn + '(s:outText, ' + options + ')' + nl; 80 | c += 'let s:outText2 = s:result2.text' + nl; 81 | c += 'if s:outText !=# s:expectedText' + nl; 82 | c += ' let s:anyErrorsFound = 1' + nl; 83 | c += ' silent !echo "' + modeStr + ' In/Out test ' + testId + ' failed"' + nl; 84 | c += 'endif' + nl; 85 | c += 'if s:outText2 !=# s:expectedText' + nl; 86 | c += ' let s:anyErrorsFound = 1' + nl; 87 | c += ' silent !echo "' + modeStr + ' Idempotence test ' + testId + ' failed"' + nl; 88 | c += 'endif' + nl; 89 | 90 | // test cross-mode preservation if there are no options 91 | if (options === '{}') { 92 | c += 'let s:result3 = ' + oppositeFn + '(s:outText, ' + options + ')' + nl; 93 | c += 'let s:outText3 = s:result3.text' + nl; 94 | c += 'if s:outText3 !=# s:expectedText' + nl; 95 | c += ' let s:anyErrorsFound = 1' + nl; 96 | c += ' silent !echo "' + modeStr + ' Cross-mode preservation test ' + testId + ' failed"' + nl; 97 | c += 'endif' + nl; 98 | } 99 | 100 | return c; 101 | } 102 | 103 | function buildOptions(opts) { 104 | var optionsStr = '{'; 105 | if (opts) { 106 | if (opts.hasOwnProperty('cursorX')) { optionsStr += "'cursorX':" + opts.cursorX + ','; } 107 | if (opts.hasOwnProperty('cursorLine')) { optionsStr += "'cursorLine':" + opts.cursorLine + ','; } 108 | if (opts.hasOwnProperty('cursorDx')) { optionsStr += "'cursorDx':" + opts.cursorDx + ','; } 109 | } 110 | optionsStr += '}'; 111 | 112 | return optionsStr 113 | } 114 | 115 | function escapeVimlString(s) { 116 | return JSON.stringify(s); 117 | } 118 | -------------------------------------------------------------------------------- /parinfer.vim: -------------------------------------------------------------------------------- 1 | "" parinfer.vim - a Parinfer implementation in Vimscript 2 | "" v0.2.0 3 | "" https://github.com/oakmac/parinfer-viml 4 | "" 5 | "" More information about Parinfer can be found here: 6 | "" http://shaunlebron.github.io/parinfer/ 7 | "" 8 | "" Copyright (c) 2016, Chris Oakman 9 | "" Released under the ISC license 10 | "" https://github.com/oakmac/parinfer-viml/blob/master/LICENSE.md 11 | 12 | ""------------------------------------------------------------------------------ 13 | "" Constants / Predicates 14 | ""------------------------------------------------------------------------------ 15 | 16 | let s:SENTINEL_NULL = -999 17 | 18 | let s:INDENT_MODE = 'INDENT_MODE' 19 | let s:PAREN_MODE = 'PAREN_MODE' 20 | 21 | let s:BACKSLASH = '\' 22 | let s:BLANK_SPACE = ' ' 23 | let s:DOUBLE_SPACE = ' ' 24 | let s:DOUBLE_QUOTE = '"' 25 | let s:NEWLINE = "\n" 26 | let s:SEMICOLON = ';' 27 | let s:TAB = "\t" 28 | 29 | let s:PARENS = {} 30 | let s:PARENS['('] = ')' 31 | let s:PARENS['{'] = '}' 32 | let s:PARENS['['] = ']' 33 | let s:PARENS[')'] = '(' 34 | let s:PARENS['}'] = '{' 35 | let s:PARENS[']'] = '[' 36 | 37 | " Parser 'states' 38 | let s:CODE = '*' 39 | let s:COMMENT = ';' 40 | let s:STRING = '"' 41 | 42 | function! s:IsCloseParen(ch) 43 | return a:ch ==# ')' || a:ch ==# '}' || a:ch ==# ']' 44 | endfunction 45 | 46 | ""------------------------------------------------------------------------------ 47 | "" Result Structure 48 | ""------------------------------------------------------------------------------ 49 | 50 | function! s:CreateInitialResult(text, mode, options) 51 | let l:result = {} 52 | 53 | let l:result.mode = a:mode 54 | 55 | let l:result.origText = a:text 56 | let l:result.origLines = split(a:text, s:NEWLINE, 1) 57 | 58 | let l:result.lines = [] 59 | let l:result.lineNo = -1 60 | let l:result.ch = '' 61 | let l:result.x = 0 62 | 63 | let l:result.parenStack = [] 64 | 65 | let l:result.parenTrailLineNo = s:SENTINEL_NULL 66 | let l:result.parenTrailStartX = s:SENTINEL_NULL 67 | let l:result.parenTrailEndX = s:SENTINEL_NULL 68 | let l:result.parenTrailOpeners = [] 69 | 70 | let l:result.cursorX = get(a:options, 'cursorX', s:SENTINEL_NULL) 71 | let l:result.cursorLine = get(a:options, 'cursorLine', s:SENTINEL_NULL) 72 | let l:result.cursorDx = get(a:options, 'cursorDx', s:SENTINEL_NULL) 73 | 74 | let l:result.state = s:CODE 75 | let l:result.commentX = s:SENTINEL_NULL 76 | 77 | let l:result.quoteDanger = 0 78 | let l:result.trackingIndent = 0 79 | let l:result.skipChar = 0 80 | let l:result.success = 0 81 | 82 | let l:result.maxIndent = s:SENTINEL_NULL 83 | let l:result.indentDelta = 0 84 | 85 | let l:result.error = s:SENTINEL_NULL 86 | let l:result.errorPosCache = {} 87 | 88 | return l:result 89 | endfunction 90 | 91 | ""------------------------------------------------------------------------------ 92 | "" Errors 93 | ""------------------------------------------------------------------------------ 94 | 95 | let s:ERROR_QUOTE_DANGER = 'quote-danger' 96 | let s:ERROR_EOL_BACKSLASH = 'eol-backslash' 97 | let s:ERROR_UNCLOSED_QUOTE = 'unclosed-quote' 98 | let s:ERROR_UNCLOSED_PAREN = 'unclosed-paren' 99 | 100 | let s:ERROR_MESSAGES = {} 101 | let s:ERROR_MESSAGES[s:ERROR_QUOTE_DANGER] = 'Quotes must balanced inside comment blocks.' 102 | let s:ERROR_MESSAGES[s:ERROR_EOL_BACKSLASH] = 'Line cannot end in a hanging backslash.' 103 | let s:ERROR_MESSAGES[s:ERROR_UNCLOSED_QUOTE] = 'String is missing a closing quote.' 104 | let s:ERROR_MESSAGES[s:ERROR_UNCLOSED_PAREN] = 'Unmatched open-paren.' 105 | 106 | 107 | function! s:CacheErrorPos(result, errorName, lineNo, x) 108 | let a:result.errorPosCache[a:errorName] = {'lineNo': a:lineNo, 'x': a:x,} 109 | endfunction 110 | 111 | function! s:CreateError(result, errorName, lineNo, x) 112 | "" TODO: figure out how to attach information to a Vimscript error 113 | return 'PARINFER_ERROR' 114 | endfunction 115 | 116 | 117 | ""------------------------------------------------------------------------------ 118 | "" String Operations 119 | ""------------------------------------------------------------------------------ 120 | 121 | 122 | function! s:InsertWithinString(orig, idx, insert) 123 | return strpart(a:orig, 0, a:idx) . a:insert . strpart(a:orig, a:idx) 124 | endfunction 125 | 126 | 127 | function! s:RemoveWithinString(orig, startIdx, endIdx) 128 | return strpart(a:orig, 0, a:startIdx) . strpart(a:orig, a:endIdx) 129 | endfunction 130 | 131 | 132 | ""------------------------------------------------------------------------------ 133 | "" Line operations 134 | ""------------------------------------------------------------------------------ 135 | 136 | 137 | function! s:InsertWithinLine(result, lineNo, idx, insert) 138 | let a:result.lines[a:lineNo] = s:InsertWithinString(a:result.lines[a:lineNo], a:idx, a:insert) 139 | endfunction 140 | 141 | 142 | function! s:ReplaceWithinLine(result, lineNo, startIdx, endIdx, replace) 143 | let a:result.lines[a:lineNo] = strpart(a:result.lines[a:lineNo], 0, a:startIdx) . a:replace . strpart(a:result.lines[a:lineNo], a:endIdx) 144 | endfunction 145 | 146 | 147 | function! s:RemoveWithinLine(result, lineNo, startIdx, endIdx) 148 | let a:result.lines[a:lineNo] = s:RemoveWithinString(a:result.lines[a:lineNo], a:startIdx, a:endIdx) 149 | endfunction 150 | 151 | 152 | function! s:InitLine(result, line) 153 | let a:result.x = 0 154 | let a:result.lineNo = a:result.lineNo + 1 155 | call add(a:result.lines, a:line) 156 | 157 | "" reset line-specific state 158 | let a:result.commentX = s:SENTINEL_NULL 159 | let a:result.indentDelta = 0 160 | endfunction 161 | 162 | 163 | ""------------------------------------------------------------------------------ 164 | "" Misc Util 165 | ""------------------------------------------------------------------------------ 166 | 167 | "" NOTE: this should be a variadic function, but for Parinfer's purposes it only 168 | "" needs to be two arity 169 | function! s:Max(x, y) 170 | if a:y > a:x 171 | return a:y 172 | endif 173 | return a:x 174 | endfunction 175 | 176 | 177 | function! s:Clamp(valN, minN, maxN) 178 | let l:returnVal = a:valN 179 | if a:minN != s:SENTINEL_NULL 180 | if a:minN > l:returnVal 181 | let l:returnVal = a:minN 182 | endif 183 | endif 184 | 185 | if a:maxN != s:SENTINEL_NULL 186 | if a:maxN < l:returnVal 187 | let l:returnVal = a:maxN 188 | endif 189 | endif 190 | 191 | return l:returnVal 192 | endfunction 193 | 194 | 195 | function! s:Peek(arr) 196 | return get(a:arr, -1, s:SENTINEL_NULL) 197 | endfunction 198 | 199 | 200 | "" removes the last item from a list 201 | "" if the list is already empty, does nothing 202 | "" returns the modified (or empty) array 203 | function! s:Pop(arr) 204 | if len(a:arr) == 0 205 | return [] 206 | endif 207 | return a:arr[0:-2] 208 | endfunction 209 | 210 | 211 | ""------------------------------------------------------------------------------ 212 | "" Character functions 213 | ""------------------------------------------------------------------------------ 214 | 215 | 216 | function! s:IsValidCloseParen(parenStack, ch) 217 | return len(a:parenStack) ? s:Peek(a:parenStack).ch ==# s:PARENS[a:ch] : 0 218 | endfunction 219 | 220 | 221 | function! s:OnOpenParen(result) 222 | call add(a:result.parenStack, { "lineNo": a:result.lineNo 223 | \ , "x": a:result.x 224 | \ , "ch": a:result.ch 225 | \ , "indentDelta": a:result.indentDelta 226 | \ }) 227 | endfunction 228 | 229 | 230 | function! s:OnMatchedCloseParen(result) 231 | let l:opener = s:Peek(a:result.parenStack) 232 | let a:result.parenTrailEndX = a:result.x + 1 233 | call add(a:result.parenTrailOpeners, l:opener) 234 | let a:result.maxIndent = l:opener.x 235 | let a:result.parenStack = s:Pop(a:result.parenStack) 236 | endfunction 237 | 238 | 239 | function! s:OnUnmatchedCloseParen(result) 240 | let a:result.ch = '' 241 | endfunction 242 | 243 | 244 | function! s:OnCloseParen(result) 245 | if s:IsValidCloseParen(a:result.parenStack, a:result.ch) 246 | call s:OnMatchedCloseParen(a:result) 247 | else 248 | call s:OnUnmatchedCloseParen(a:result) 249 | endif 250 | endfunction 251 | 252 | 253 | function! s:OnTab(result) 254 | let a:result.ch = s:DOUBLE_SPACE 255 | endfunction 256 | 257 | 258 | function! s:OnSemicolon(result) 259 | let a:result.state = s:COMMENT 260 | let a:result.commentX = a:result.x 261 | endfunction 262 | 263 | 264 | function! s:OnCommentNewline(result) 265 | let a:result.state = s:CODE 266 | let a:result.ch = '' 267 | endfunction 268 | 269 | 270 | function! s:OnNewline(result) 271 | let a:result.ch = '' 272 | endfunction 273 | 274 | 275 | function! s:OnCodeQuote(result) 276 | let a:result.state = s:STRING 277 | call s:CacheErrorPos(a:result, s:ERROR_UNCLOSED_QUOTE, a:result.lineNo, a:result.x) 278 | endfunction 279 | 280 | 281 | function! s:OnStringQuote(result) 282 | let a:result.state = s:CODE 283 | endfunction 284 | 285 | 286 | function! s:OnCommentQuote(result) 287 | let a:result.quoteDanger = ! a:result.quoteDanger 288 | if a:result.quoteDanger 289 | call s:CacheErrorPos(a:result, s:ERROR_QUOTE_DANGER, a:result.lineNo, a:result.x) 290 | endif 291 | endfunction 292 | 293 | 294 | function! s:OnBackslashNewline(result) 295 | throw s:CreateError(a:result, s:ERROR_EOL_BACKSLASH, a:result.lineNo, a:result.x - 1) 296 | endfunction 297 | 298 | let s:DISPATCH = 299 | \ { '*(': function("OnOpenParen") 300 | \ , '*[': function("OnOpenParen") 301 | \ , '*{': function("OnOpenParen") 302 | \ , '*)': function("OnCloseParen") 303 | \ , '*]': function("OnCloseParen") 304 | \ , '*}': function("OnCloseParen") 305 | \ , '*"': function("OnCodeQuote") 306 | \ , '*;': function("OnSemicolon") 307 | \ , "*\t": function("OnTab") 308 | \ , "*\n": function("OnNewline") 309 | \ , "*\\\n": function("OnBackslashNewline") 310 | \ , ';"': function("OnCommentQuote") 311 | \ , ";\n": function("OnCommentNewline") 312 | \ , '""': function("OnStringQuote") 313 | \ , "\"\n": function("OnNewline") 314 | \ } 315 | 316 | ""------------------------------------------------------------------------------ 317 | "" Cursor functions 318 | ""------------------------------------------------------------------------------ 319 | 320 | 321 | function! s:IsCursorOnLeft(result) 322 | return a:result.lineNo == a:result.cursorLine && 323 | \ a:result.cursorX != s:SENTINEL_NULL && 324 | \ a:result.cursorX <= a:result.x 325 | endfunction 326 | 327 | 328 | function! s:IsCursorOnRight(result, x) 329 | return a:result.lineNo == a:result.cursorLine && 330 | \ a:result.cursorX != s:SENTINEL_NULL && 331 | \ a:x != s:SENTINEL_NULL && 332 | \ a:result.cursorX > a:x 333 | endfunction 334 | 335 | 336 | function! s:IsCursorInComment(result) 337 | return s:IsCursorOnRight(a:result, a:result.commentX) 338 | endfunction 339 | 340 | 341 | function! s:HandleCursorDelta(result) 342 | let l:hasCursorDelta = a:result.cursorDx != s:SENTINEL_NULL && 343 | \ a:result.cursorLine == a:result.lineNo && 344 | \ a:result.cursorX == a:result.x 345 | 346 | if l:hasCursorDelta 347 | let a:result.indentDelta = a:result.indentDelta + a:result.cursorDx 348 | endif 349 | endfunction 350 | 351 | 352 | ""------------------------------------------------------------------------------ 353 | "" Paren Trail functions 354 | ""------------------------------------------------------------------------------ 355 | 356 | 357 | function! s:ClampParenTrailToCursor(result) 358 | let l:startX = a:result.parenTrailStartX 359 | let l:endX = a:result.parenTrailEndX 360 | 361 | let l:isCursorClamping = s:IsCursorOnRight(a:result, l:startX) && 362 | \ ! s:IsCursorInComment(a:result) 363 | 364 | if l:isCursorClamping 365 | let l:newStartX = s:Max(l:startX, a:result.cursorX) 366 | let l:newEndX = s:Max(l:endX, a:result.cursorX) 367 | 368 | let l:line = a:result.lines[a:result.lineNo] 369 | let l:removeCount = 0 370 | let l:i = l:startX 371 | while l:i < l:newStartX 372 | if s:IsCloseParen(l:line[l:i]) 373 | let l:removeCount = l:removeCount + 1 374 | endif 375 | let l:i = l:i + 1 376 | endwhile 377 | 378 | if l:removeCount > 0 379 | let a:result.parenTrailOpeners = a:result.parenTrailOpeners[l:removeCount : ] 380 | endif 381 | let a:result.parenTrailStartX = l:newStartX 382 | let a:result.parenTrailEndX = l:newEndX 383 | endif 384 | endfunction 385 | 386 | 387 | function! s:RemoveParenTrail(result) 388 | let l:startX = a:result.parenTrailStartX 389 | let l:endX = a:result.parenTrailEndX 390 | 391 | if l:startX == l:endX 392 | return 393 | endif 394 | 395 | let l:openers = a:result.parenTrailOpeners 396 | let a:result.parenStack = a:result.parenStack + reverse(l:openers) 397 | let a:result.parenTrailOpeners = [] 398 | 399 | call s:RemoveWithinLine(a:result, a:result.lineNo, l:startX, l:endX) 400 | endfunction 401 | 402 | 403 | function! s:CorrectParenTrail(result, indentX) 404 | let l:parens = '' 405 | 406 | while len(a:result.parenStack) > 0 407 | let l:opener = s:Peek(a:result.parenStack) 408 | if l:opener.x >= a:indentX 409 | let a:result.parenStack = s:Pop(a:result.parenStack) 410 | let l:parens = l:parens . s:PARENS[l:opener.ch] 411 | else 412 | break 413 | endif 414 | endwhile 415 | 416 | call s:InsertWithinLine(a:result, a:result.parenTrailLineNo, a:result.parenTrailStartX, l:parens) 417 | endfunction 418 | 419 | 420 | function! s:CleanParenTrail(result) 421 | let l:startX = a:result.parenTrailStartX 422 | let l:endX = a:result.parenTrailEndX 423 | 424 | if l:startX == l:endX || a:result.lineNo != a:result.parenTrailLineNo 425 | return 426 | endif 427 | 428 | let l:line = a:result.lines[a:result.lineNo] 429 | let l:newTrail = '' 430 | let l:spaceCount = 0 431 | let l:i = l:startX 432 | while l:i < l:endX 433 | if s:IsCloseParen(l:line[l:i]) 434 | let l:newTrail = l:newTrail . l:line[l:i] 435 | else 436 | let l:spaceCount = l:spaceCount + 1 437 | endif 438 | let l:i = l:i + 1 439 | endwhile 440 | 441 | if l:spaceCount > 0 442 | call s:ReplaceWithinLine(a:result, a:result.lineNo, l:startX, l:endX, l:newTrail) 443 | let a:result.parenTrailEndX = a:result.parenTrailEndX - l:spaceCount 444 | endif 445 | endfunction 446 | 447 | 448 | function! s:AppendParenTrail(result) 449 | let l:opener = a:result.parenStack[-1] 450 | let a:result.parenStack = s:Pop(a:result.parenStack) 451 | let l:closeCh = s:PARENS[l:opener.ch] 452 | 453 | let a:result.maxIndent = l:opener.x 454 | call s:InsertWithinLine(a:result, a:result.parenTrailLineNo, a:result.parenTrailEndX, l:closeCh) 455 | let a:result.parenTrailEndX = a:result.parenTrailEndX + 1 456 | endfunction 457 | 458 | 459 | function! s:FinishNewParenTrail(result) 460 | if a:result.mode ==# s:INDENT_MODE 461 | call s:ClampParenTrailToCursor(a:result) 462 | call s:RemoveParenTrail(a:result) 463 | elseif a:result.mode ==# s:PAREN_MODE 464 | if a:result.lineNo != a:result.cursorLine 465 | call s:CleanParenTrail(a:result) 466 | endif 467 | endif 468 | endfunction 469 | 470 | ""------------------------------------------------------------------------------ 471 | "" Indentation functions 472 | ""------------------------------------------------------------------------------ 473 | 474 | function! s:CorrectIndent(result) 475 | let l:origIndent = a:result.x 476 | let l:newIndent = l:origIndent 477 | let l:minIndent = 0 478 | let l:maxIndent = a:result.maxIndent 479 | 480 | if len(a:result.parenStack) != 0 481 | let l:opener = s:Peek(a:result.parenStack) 482 | let l:minIndent = l:opener.x + 1 483 | let l:newIndent = l:newIndent + l:opener.indentDelta 484 | endif 485 | 486 | let l:newIndent = s:Clamp(l:newIndent, l:minIndent, l:maxIndent) 487 | 488 | if l:newIndent != l:origIndent 489 | let l:indentStr = repeat(s:BLANK_SPACE, l:newIndent) 490 | call s:ReplaceWithinLine(a:result, a:result.lineNo, 0, l:origIndent, l:indentStr) 491 | let a:result.x = l:newIndent 492 | let a:result.indentDelta = a:result.indentDelta + l:newIndent - l:origIndent 493 | endif 494 | endfunction 495 | 496 | 497 | function! s:OnProperIndent(result) 498 | let a:result.trackingIndent = 0 499 | 500 | if a:result.quoteDanger 501 | throw s:CreateError(a:result, s:ERROR_QUOTE_DANGER, s:SENTINEL_NULL, s:SENTINEL_NULL) 502 | endif 503 | 504 | if a:result.mode ==# s:INDENT_MODE 505 | call s:CorrectParenTrail(a:result, a:result.x) 506 | elseif a:result.mode ==# s:PAREN_MODE 507 | call s:CorrectIndent(a:result) 508 | endif 509 | endfunction 510 | 511 | 512 | function! s:OnLeadingCloseParen(result) 513 | let a:result.skipChar = 1 514 | let a:result.trackingIndent = 1 515 | 516 | if a:result.mode ==# s:PAREN_MODE 517 | if s:IsValidCloseParen(a:result.parenStack, a:result.ch) 518 | if s:IsCursorOnLeft(a:result) 519 | let a:result.skipChar = 0 520 | call s:OnProperIndent(a:result) 521 | else 522 | call s:AppendParenTrail(a:result) 523 | endif 524 | endif 525 | endif 526 | endfunction 527 | 528 | 529 | function! s:OnIndent(result) 530 | if s:IsCloseParen(a:result.ch) 531 | call s:OnLeadingCloseParen(a:result) 532 | elseif a:result.ch ==# s:SEMICOLON 533 | let a:result.trackingIndent = 0 534 | elseif a:result.ch !=# s:NEWLINE 535 | call s:OnProperIndent(a:result) 536 | endif 537 | endfunction 538 | 539 | 540 | ""------------------------------------------------------------------------------ 541 | "" High-level processing functions 542 | ""------------------------------------------------------------------------------ 543 | 544 | function! s:ProcessChar(result, ch) 545 | let a:result.ch = a:ch 546 | let a:result.skipChar = 0 547 | 548 | if a:result.mode ==# s:PAREN_MODE 549 | call s:HandleCursorDelta(a:result) 550 | endif 551 | 552 | if a:result.trackingIndent && a:ch !=# s:BLANK_SPACE && a:ch !=# s:TAB 553 | call s:OnIndent(a:result) 554 | endif 555 | 556 | if a:result.skipChar 557 | let a:result.ch = '' 558 | else 559 | call call(get(s:DISPATCH, a:result.state . a:result.ch, function("type")), [a:result]) 560 | " UpdateParenTrailBounds 561 | if a:result.state ==# s:CODE && 562 | \ a:result.ch =~ '[^)\]}]' && 563 | \ (a:result.ch !=# ' ' || (a:result.x > 0 && a:result.lines[a:result.lineNo][a:result.x - 1] ==# '\')) 564 | call extend(a:result, { "parenTrailLineNo": a:result.lineNo 565 | \ , "parenTrailStartX": a:result.x + strlen(a:result.ch) 566 | \ , "parenTrailEndX": a:result.x + strlen(a:result.ch) 567 | \ , "parenTrailOpeners": [] 568 | \ , "maxIndent": s:SENTINEL_NULL 569 | \ }) 570 | endif 571 | endif 572 | 573 | " CommitChar 574 | if a:ch !=# a:result.ch 575 | call s:ReplaceWithinLine(a:result, a:result.lineNo, a:result.x, a:result.x + strlen(a:ch), a:result.ch) 576 | endif 577 | let a:result.x += strlen(a:result.ch) 578 | endfunction 579 | 580 | function! s:ProcessLine(result, line) 581 | call s:InitLine(a:result, a:line) 582 | 583 | if a:result.mode ==# s:INDENT_MODE 584 | let a:result.trackingIndent = len(a:result.parenStack) != 0 && 585 | \ a:result.state !=# s:STRING 586 | elseif a:result.mode ==# s:PAREN_MODE 587 | let a:result.trackingIndent = a:result.state !=# s:STRING 588 | endif 589 | 590 | call map(split(a:line . s:NEWLINE, '\%([ ()[\]{}";\t\n]\|\\[.\n]\|[^ ()[\]{}";\\\t\n]\+\)\zs'), 's:ProcessChar(a:result, v:val)') 591 | 592 | if a:result.lineNo == a:result.parenTrailLineNo 593 | call s:FinishNewParenTrail(a:result) 594 | endif 595 | endfunction 596 | 597 | 598 | function! s:FinalizeResult(result) 599 | if a:result.quoteDanger 600 | throw s:CreateError(a:result, s:ERROR_QUOTE_DANGER, s:SENTINEL_NULL, s:SENTINEL_NULL) 601 | endif 602 | 603 | if a:result.state ==# s:STRING 604 | throw s:CreateError(a:result, s:ERROR_UNCLOSED_QUOTE, s:SENTINEL_NULL, s:SENTINEL_NULL) 605 | endif 606 | 607 | if len(a:result.parenStack) != 0 608 | if a:result.mode ==# s:PAREN_MODE 609 | let l:opener = s:Peek(a:result.parenStack) 610 | throw s:CreateError(a:result, s:ERROR_UNCLOSED_PAREN, l:opener.lineNo, l:opener.x) 611 | elseif a:result.mode ==# s:INDENT_MODE 612 | call s:CorrectParenTrail(a:result, 0) 613 | endif 614 | endif 615 | let a:result.success = 1 616 | endfunction 617 | 618 | 619 | function! s:ProcessError(result, err) 620 | let a:result.success = 0 621 | 622 | "" TODO: figure out how to attach error information to a throw 623 | "" let a:result.error = a:err 624 | endfunction 625 | 626 | 627 | function! s:ProcessText(text, mode, options) 628 | let l:result = s:CreateInitialResult(a:text, a:mode, a:options) 629 | 630 | try 631 | call map(copy(l:result.origLines), 's:ProcessLine(l:result, v:val)') 632 | call s:FinalizeResult(l:result) 633 | catch /PARINFER_ERROR/ 634 | call s:ProcessError(l:result, {}) 635 | endtry 636 | 637 | return l:result 638 | endfunction 639 | 640 | function! s:PublicResult(result) 641 | if ! a:result.success 642 | let l:result = {} 643 | let l:result.success = 0 644 | let l:result.text = a:result.origText 645 | let l:result.error = a:result.error 646 | return l:result 647 | endif 648 | 649 | let l:result = {} 650 | let l:result.success = 1 651 | let l:result.text = join(a:result.lines, s:NEWLINE) 652 | return l:result 653 | endfunction 654 | 655 | ""------------------------------------------------------------------------------ 656 | "" Public API 657 | ""------------------------------------------------------------------------------ 658 | 659 | let s:PublicAPI = {} 660 | let g:ParinferLib = s:PublicAPI 661 | 662 | function! s:PublicAPI.IndentMode(text, options) 663 | let l:result = s:ProcessText(a:text, s:INDENT_MODE, a:options) 664 | return s:PublicResult(l:result) 665 | endfunction 666 | 667 | function! s:PublicAPI.ParenMode(text, options) 668 | let l:result = s:ProcessText(a:text, s:PAREN_MODE, a:options) 669 | return s:PublicResult(l:result) 670 | endfunction 671 | -------------------------------------------------------------------------------- /perf.vim: -------------------------------------------------------------------------------- 1 | 2 | source parinfer.vim 3 | 4 | " Open our input file and copy it to register 'a' 5 | e tests/really_long_file 6 | normal ggVG"ay 7 | let s:text = @a 8 | 9 | " Time Indent Mode 10 | let s:start = reltime() 11 | let s:result = g:ParinferLib.IndentMode(s:text, {}) 12 | let s:time = reltime(s:start) 13 | execute 'silent !echo Indent Mode:' reltimestr(s:time) 14 | 15 | " Time Paren Mode 16 | let s:start = reltime() 17 | let s:result = g:ParinferLib.ParenMode(s:text, {}) 18 | let s:time = reltime(s:start) 19 | execute 'silent !echo Paren Mode:' reltimestr(s:time) 20 | 21 | quit 22 | -------------------------------------------------------------------------------- /tests.vim: -------------------------------------------------------------------------------- 1 | ""!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 2 | ""!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 3 | "" 4 | "" NOTE: This file is automatically generated from build-tests-file.js 5 | "" Please do not edit it directly 6 | "" 7 | ""!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 8 | ""!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 9 | 10 | source parinfer.vim 11 | let s:anyErrorsFound = 0 12 | 13 | ""~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 14 | "" Indent Mode Tests 15 | ""~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 16 | 17 | "" Test Id: 6 18 | let s:result = g:ParinferLib.IndentMode("(defn foo\n [arg\n ret", {}) 19 | let s:expectedText = "(defn foo\n [arg]\n ret)" 20 | let s:outText = s:result.text 21 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 22 | let s:outText2 = s:result2.text 23 | if s:outText !=# s:expectedText 24 | let s:anyErrorsFound = 1 25 | silent !echo "Indent Mode In/Out test 6 failed" 26 | endif 27 | if s:outText2 !=# s:expectedText 28 | let s:anyErrorsFound = 1 29 | silent !echo "Indent Mode Idempotence test 6 failed" 30 | endif 31 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 32 | let s:outText3 = s:result3.text 33 | if s:outText3 !=# s:expectedText 34 | let s:anyErrorsFound = 1 35 | silent !echo "Indent Mode Cross-mode preservation test 6 failed" 36 | endif 37 | 38 | 39 | "" Test Id: 20 40 | let s:result = g:ParinferLib.IndentMode("(defn foo\n [arg\n ret", {}) 41 | let s:expectedText = "(defn foo\n [arg\n ret])" 42 | let s:outText = s:result.text 43 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 44 | let s:outText2 = s:result2.text 45 | if s:outText !=# s:expectedText 46 | let s:anyErrorsFound = 1 47 | silent !echo "Indent Mode In/Out test 20 failed" 48 | endif 49 | if s:outText2 !=# s:expectedText 50 | let s:anyErrorsFound = 1 51 | silent !echo "Indent Mode Idempotence test 20 failed" 52 | endif 53 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 54 | let s:outText3 = s:result3.text 55 | if s:outText3 !=# s:expectedText 56 | let s:anyErrorsFound = 1 57 | silent !echo "Indent Mode Cross-mode preservation test 20 failed" 58 | endif 59 | 60 | 61 | "" Test Id: 34 62 | let s:result = g:ParinferLib.IndentMode("(defn foo\n[arg\n ret", {}) 63 | let s:expectedText = "(defn foo)\n[arg\n ret]" 64 | let s:outText = s:result.text 65 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 66 | let s:outText2 = s:result2.text 67 | if s:outText !=# s:expectedText 68 | let s:anyErrorsFound = 1 69 | silent !echo "Indent Mode In/Out test 34 failed" 70 | endif 71 | if s:outText2 !=# s:expectedText 72 | let s:anyErrorsFound = 1 73 | silent !echo "Indent Mode Idempotence test 34 failed" 74 | endif 75 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 76 | let s:outText3 = s:result3.text 77 | if s:outText3 !=# s:expectedText 78 | let s:anyErrorsFound = 1 79 | silent !echo "Indent Mode Cross-mode preservation test 34 failed" 80 | endif 81 | 82 | 83 | "" Test Id: 48 84 | let s:result = g:ParinferLib.IndentMode("(defn foo\n[arg\nret", {}) 85 | let s:expectedText = "(defn foo)\n[arg]\nret" 86 | let s:outText = s:result.text 87 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 88 | let s:outText2 = s:result2.text 89 | if s:outText !=# s:expectedText 90 | let s:anyErrorsFound = 1 91 | silent !echo "Indent Mode In/Out test 48 failed" 92 | endif 93 | if s:outText2 !=# s:expectedText 94 | let s:anyErrorsFound = 1 95 | silent !echo "Indent Mode Idempotence test 48 failed" 96 | endif 97 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 98 | let s:outText3 = s:result3.text 99 | if s:outText3 !=# s:expectedText 100 | let s:anyErrorsFound = 1 101 | silent !echo "Indent Mode Cross-mode preservation test 48 failed" 102 | endif 103 | 104 | 105 | "" Test Id: 62 106 | let s:result = g:ParinferLib.IndentMode("(defn foo\n [arg\n ret\n\n(defn foo\n [arg\n ret", {}) 107 | let s:expectedText = "(defn foo\n [arg]\n ret)\n\n(defn foo\n [arg]\n ret)" 108 | let s:outText = s:result.text 109 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 110 | let s:outText2 = s:result2.text 111 | if s:outText !=# s:expectedText 112 | let s:anyErrorsFound = 1 113 | silent !echo "Indent Mode In/Out test 62 failed" 114 | endif 115 | if s:outText2 !=# s:expectedText 116 | let s:anyErrorsFound = 1 117 | silent !echo "Indent Mode Idempotence test 62 failed" 118 | endif 119 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 120 | let s:outText3 = s:result3.text 121 | if s:outText3 !=# s:expectedText 122 | let s:anyErrorsFound = 1 123 | silent !echo "Indent Mode Cross-mode preservation test 62 failed" 124 | endif 125 | 126 | 127 | "" Test Id: 86 128 | let s:result = g:ParinferLib.IndentMode("(def foo [a b]]", {}) 129 | let s:expectedText = "(def foo [a b])" 130 | let s:outText = s:result.text 131 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 132 | let s:outText2 = s:result2.text 133 | if s:outText !=# s:expectedText 134 | let s:anyErrorsFound = 1 135 | silent !echo "Indent Mode In/Out test 86 failed" 136 | endif 137 | if s:outText2 !=# s:expectedText 138 | let s:anyErrorsFound = 1 139 | silent !echo "Indent Mode Idempotence test 86 failed" 140 | endif 141 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 142 | let s:outText3 = s:result3.text 143 | if s:outText3 !=# s:expectedText 144 | let s:anyErrorsFound = 1 145 | silent !echo "Indent Mode Cross-mode preservation test 86 failed" 146 | endif 147 | 148 | 149 | "" Test Id: 96 150 | let s:result = g:ParinferLib.IndentMode("(let [x {:foo 1 :bar 2]\n x)", {}) 151 | let s:expectedText = "(let [x {:foo 1 :bar 2}]\n x)" 152 | let s:outText = s:result.text 153 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 154 | let s:outText2 = s:result2.text 155 | if s:outText !=# s:expectedText 156 | let s:anyErrorsFound = 1 157 | silent !echo "Indent Mode In/Out test 96 failed" 158 | endif 159 | if s:outText2 !=# s:expectedText 160 | let s:anyErrorsFound = 1 161 | silent !echo "Indent Mode Idempotence test 96 failed" 162 | endif 163 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 164 | let s:outText3 = s:result3.text 165 | if s:outText3 !=# s:expectedText 166 | let s:anyErrorsFound = 1 167 | silent !echo "Indent Mode Cross-mode preservation test 96 failed" 168 | endif 169 | 170 | 171 | "" Test Id: 110 172 | let s:result = g:ParinferLib.IndentMode("(def foo \"as", {}) 173 | let s:expectedText = "(def foo \"as" 174 | let s:outText = s:result.text 175 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 176 | let s:outText2 = s:result2.text 177 | if s:outText !=# s:expectedText 178 | let s:anyErrorsFound = 1 179 | silent !echo "Indent Mode In/Out test 110 failed" 180 | endif 181 | if s:outText2 !=# s:expectedText 182 | let s:anyErrorsFound = 1 183 | silent !echo "Indent Mode Idempotence test 110 failed" 184 | endif 185 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 186 | let s:outText3 = s:result3.text 187 | if s:outText3 !=# s:expectedText 188 | let s:anyErrorsFound = 1 189 | silent !echo "Indent Mode Cross-mode preservation test 110 failed" 190 | endif 191 | 192 | 193 | "" Test Id: 120 194 | let s:result = g:ParinferLib.IndentMode("(defn foo [a \"])", {}) 195 | let s:expectedText = "(defn foo [a \"])" 196 | let s:outText = s:result.text 197 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 198 | let s:outText2 = s:result2.text 199 | if s:outText !=# s:expectedText 200 | let s:anyErrorsFound = 1 201 | silent !echo "Indent Mode In/Out test 120 failed" 202 | endif 203 | if s:outText2 !=# s:expectedText 204 | let s:anyErrorsFound = 1 205 | silent !echo "Indent Mode Idempotence test 120 failed" 206 | endif 207 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 208 | let s:outText3 = s:result3.text 209 | if s:outText3 !=# s:expectedText 210 | let s:anyErrorsFound = 1 211 | silent !echo "Indent Mode Cross-mode preservation test 120 failed" 212 | endif 213 | 214 | 215 | "" Test Id: 130 216 | let s:result = g:ParinferLib.IndentMode("(defn foo\n \"This is docstring.\n Line 2 here.\"\n ret", {}) 217 | let s:expectedText = "(defn foo\n \"This is docstring.\n Line 2 here.\"\n ret)" 218 | let s:outText = s:result.text 219 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 220 | let s:outText2 = s:result2.text 221 | if s:outText !=# s:expectedText 222 | let s:anyErrorsFound = 1 223 | silent !echo "Indent Mode In/Out test 130 failed" 224 | endif 225 | if s:outText2 !=# s:expectedText 226 | let s:anyErrorsFound = 1 227 | silent !echo "Indent Mode Idempotence test 130 failed" 228 | endif 229 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 230 | let s:outText3 = s:result3.text 231 | if s:outText3 !=# s:expectedText 232 | let s:anyErrorsFound = 1 233 | silent !echo "Indent Mode Cross-mode preservation test 130 failed" 234 | endif 235 | 236 | 237 | "" Test Id: 146 238 | let s:result = g:ParinferLib.IndentMode("(let [a \"Hello\nWorld\"\n b 2\n ret", {}) 239 | let s:expectedText = "(let [a \"Hello\nWorld\"\n b 2]\n ret)" 240 | let s:outText = s:result.text 241 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 242 | let s:outText2 = s:result2.text 243 | if s:outText !=# s:expectedText 244 | let s:anyErrorsFound = 1 245 | silent !echo "Indent Mode In/Out test 146 failed" 246 | endif 247 | if s:outText2 !=# s:expectedText 248 | let s:anyErrorsFound = 1 249 | silent !echo "Indent Mode Idempotence test 146 failed" 250 | endif 251 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 252 | let s:outText3 = s:result3.text 253 | if s:outText3 !=# s:expectedText 254 | let s:anyErrorsFound = 1 255 | silent !echo "Indent Mode Cross-mode preservation test 146 failed" 256 | endif 257 | 258 | 259 | "" Test Id: 162 260 | let s:result = g:ParinferLib.IndentMode("(let [a \"])\"\n b 2", {}) 261 | let s:expectedText = "(let [a \"])\"\n b 2])" 262 | let s:outText = s:result.text 263 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 264 | let s:outText2 = s:result2.text 265 | if s:outText !=# s:expectedText 266 | let s:anyErrorsFound = 1 267 | silent !echo "Indent Mode In/Out test 162 failed" 268 | endif 269 | if s:outText2 !=# s:expectedText 270 | let s:anyErrorsFound = 1 271 | silent !echo "Indent Mode Idempotence test 162 failed" 272 | endif 273 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 274 | let s:outText3 = s:result3.text 275 | if s:outText3 !=# s:expectedText 276 | let s:anyErrorsFound = 1 277 | silent !echo "Indent Mode Cross-mode preservation test 162 failed" 278 | endif 279 | 280 | 281 | "" Test Id: 174 282 | let s:result = g:ParinferLib.IndentMode("(def foo \"\\\"\"", {}) 283 | let s:expectedText = "(def foo \"\\\"\")" 284 | let s:outText = s:result.text 285 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 286 | let s:outText2 = s:result2.text 287 | if s:outText !=# s:expectedText 288 | let s:anyErrorsFound = 1 289 | silent !echo "Indent Mode In/Out test 174 failed" 290 | endif 291 | if s:outText2 !=# s:expectedText 292 | let s:anyErrorsFound = 1 293 | silent !echo "Indent Mode Idempotence test 174 failed" 294 | endif 295 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 296 | let s:outText3 = s:result3.text 297 | if s:outText3 !=# s:expectedText 298 | let s:anyErrorsFound = 1 299 | silent !echo "Indent Mode Cross-mode preservation test 174 failed" 300 | endif 301 | 302 | 303 | "" Test Id: 191 304 | let s:result = g:ParinferLib.IndentMode("\"\"]\"", {'cursorX':1,'cursorLine':0,}) 305 | let s:expectedText = "\"\"]\"" 306 | let s:outText = s:result.text 307 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {'cursorX':1,'cursorLine':0,}) 308 | let s:outText2 = s:result2.text 309 | if s:outText !=# s:expectedText 310 | let s:anyErrorsFound = 1 311 | silent !echo "Indent Mode In/Out test 191 failed" 312 | endif 313 | if s:outText2 !=# s:expectedText 314 | let s:anyErrorsFound = 1 315 | silent !echo "Indent Mode Idempotence test 191 failed" 316 | endif 317 | 318 | 319 | "" Test Id: 201 320 | let s:result = g:ParinferLib.IndentMode("(def foo\n \"\n \"(a b)\n c\")", {'cursorX':3,'cursorLine':1,}) 321 | let s:expectedText = "(def foo\n \"\n \"(a b)\n c\")" 322 | let s:outText = s:result.text 323 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {'cursorX':3,'cursorLine':1,}) 324 | let s:outText2 = s:result2.text 325 | if s:outText !=# s:expectedText 326 | let s:anyErrorsFound = 1 327 | silent !echo "Indent Mode In/Out test 201 failed" 328 | endif 329 | if s:outText2 !=# s:expectedText 330 | let s:anyErrorsFound = 1 331 | silent !echo "Indent Mode Idempotence test 201 failed" 332 | endif 333 | 334 | 335 | "" Test Id: 224 336 | let s:result = g:ParinferLib.IndentMode("(for [col columns]\n \"\n [:div.td {:style \"max-width: 500px;\"}])", {'cursorX':3,'cursorLine':1,}) 337 | let s:expectedText = "(for [col columns]\n \"\n [:div.td {:style \"max-width: 500px;\"}])" 338 | let s:outText = s:result.text 339 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {'cursorX':3,'cursorLine':1,}) 340 | let s:outText2 = s:result2.text 341 | if s:outText !=# s:expectedText 342 | let s:anyErrorsFound = 1 343 | silent !echo "Indent Mode In/Out test 224 failed" 344 | endif 345 | if s:outText2 !=# s:expectedText 346 | let s:anyErrorsFound = 1 347 | silent !echo "Indent Mode Idempotence test 224 failed" 348 | endif 349 | 350 | 351 | "" Test Id: 240 352 | let s:result = g:ParinferLib.IndentMode("(def foo [a b]\n ; \"my multiline\n ; docstring.\"\nret)", {}) 353 | let s:expectedText = "(def foo [a b])\n ; \"my multiline\n ; docstring.\"\nret" 354 | let s:outText = s:result.text 355 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 356 | let s:outText2 = s:result2.text 357 | if s:outText !=# s:expectedText 358 | let s:anyErrorsFound = 1 359 | silent !echo "Indent Mode In/Out test 240 failed" 360 | endif 361 | if s:outText2 !=# s:expectedText 362 | let s:anyErrorsFound = 1 363 | silent !echo "Indent Mode Idempotence test 240 failed" 364 | endif 365 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 366 | let s:outText3 = s:result3.text 367 | if s:outText3 !=# s:expectedText 368 | let s:anyErrorsFound = 1 369 | silent !echo "Indent Mode Cross-mode preservation test 240 failed" 370 | endif 371 | 372 | 373 | "" Test Id: 256 374 | let s:result = g:ParinferLib.IndentMode("(def foo [a b]\n ; \"\"\\\"\nret)", {}) 375 | let s:expectedText = "(def foo [a b])\n ; \"\"\\\"\nret" 376 | let s:outText = s:result.text 377 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 378 | let s:outText2 = s:result2.text 379 | if s:outText !=# s:expectedText 380 | let s:anyErrorsFound = 1 381 | silent !echo "Indent Mode In/Out test 256 failed" 382 | endif 383 | if s:outText2 !=# s:expectedText 384 | let s:anyErrorsFound = 1 385 | silent !echo "Indent Mode Idempotence test 256 failed" 386 | endif 387 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 388 | let s:outText3 = s:result3.text 389 | if s:outText3 !=# s:expectedText 390 | let s:anyErrorsFound = 1 391 | silent !echo "Indent Mode Cross-mode preservation test 256 failed" 392 | endif 393 | 394 | 395 | "" Test Id: 272 396 | let s:result = g:ParinferLib.IndentMode("(defn foo [a b\n \\[\n ret", {}) 397 | let s:expectedText = "(defn foo [a b]\n \\[\n ret)" 398 | let s:outText = s:result.text 399 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 400 | let s:outText2 = s:result2.text 401 | if s:outText !=# s:expectedText 402 | let s:anyErrorsFound = 1 403 | silent !echo "Indent Mode In/Out test 272 failed" 404 | endif 405 | if s:outText2 !=# s:expectedText 406 | let s:anyErrorsFound = 1 407 | silent !echo "Indent Mode Idempotence test 272 failed" 408 | endif 409 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 410 | let s:outText3 = s:result3.text 411 | if s:outText3 !=# s:expectedText 412 | let s:anyErrorsFound = 1 413 | silent !echo "Indent Mode Cross-mode preservation test 272 failed" 414 | endif 415 | 416 | 417 | "" Test Id: 287 418 | let s:result = g:ParinferLib.IndentMode("(def foo \\;", {}) 419 | let s:expectedText = "(def foo \\;)" 420 | let s:outText = s:result.text 421 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 422 | let s:outText2 = s:result2.text 423 | if s:outText !=# s:expectedText 424 | let s:anyErrorsFound = 1 425 | silent !echo "Indent Mode In/Out test 287 failed" 426 | endif 427 | if s:outText2 !=# s:expectedText 428 | let s:anyErrorsFound = 1 429 | silent !echo "Indent Mode Idempotence test 287 failed" 430 | endif 431 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 432 | let s:outText3 = s:result3.text 433 | if s:outText3 !=# s:expectedText 434 | let s:anyErrorsFound = 1 435 | silent !echo "Indent Mode Cross-mode preservation test 287 failed" 436 | endif 437 | 438 | 439 | "" Test Id: 297 440 | let s:result = g:ParinferLib.IndentMode("(def foo \\,\n(def bar \\ ", {}) 441 | let s:expectedText = "(def foo \\,)\n(def bar \\ )" 442 | let s:outText = s:result.text 443 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 444 | let s:outText2 = s:result2.text 445 | if s:outText !=# s:expectedText 446 | let s:anyErrorsFound = 1 447 | silent !echo "Indent Mode In/Out test 297 failed" 448 | endif 449 | if s:outText2 !=# s:expectedText 450 | let s:anyErrorsFound = 1 451 | silent !echo "Indent Mode Idempotence test 297 failed" 452 | endif 453 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 454 | let s:outText3 = s:result3.text 455 | if s:outText3 !=# s:expectedText 456 | let s:anyErrorsFound = 1 457 | silent !echo "Indent Mode Cross-mode preservation test 297 failed" 458 | endif 459 | 460 | 461 | "" Test Id: 309 462 | let s:result = g:ParinferLib.IndentMode("(foo [a b\\\n c)", {}) 463 | let s:expectedText = "(foo [a b\\\n c)" 464 | let s:outText = s:result.text 465 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 466 | let s:outText2 = s:result2.text 467 | if s:outText !=# s:expectedText 468 | let s:anyErrorsFound = 1 469 | silent !echo "Indent Mode In/Out test 309 failed" 470 | endif 471 | if s:outText2 !=# s:expectedText 472 | let s:anyErrorsFound = 1 473 | silent !echo "Indent Mode Idempotence test 309 failed" 474 | endif 475 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 476 | let s:outText3 = s:result3.text 477 | if s:outText3 !=# s:expectedText 478 | let s:anyErrorsFound = 1 479 | silent !echo "Indent Mode Cross-mode preservation test 309 failed" 480 | endif 481 | 482 | 483 | "" Test Id: 324 484 | let s:result = g:ParinferLib.IndentMode("(def foo ;)", {}) 485 | let s:expectedText = "(def foo) ;)" 486 | let s:outText = s:result.text 487 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 488 | let s:outText2 = s:result2.text 489 | if s:outText !=# s:expectedText 490 | let s:anyErrorsFound = 1 491 | silent !echo "Indent Mode In/Out test 324 failed" 492 | endif 493 | if s:outText2 !=# s:expectedText 494 | let s:anyErrorsFound = 1 495 | silent !echo "Indent Mode Idempotence test 324 failed" 496 | endif 497 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 498 | let s:outText3 = s:result3.text 499 | if s:outText3 !=# s:expectedText 500 | let s:anyErrorsFound = 1 501 | silent !echo "Indent Mode Cross-mode preservation test 324 failed" 502 | endif 503 | 504 | 505 | "" Test Id: 335 506 | let s:result = g:ParinferLib.IndentMode("(let [a 1\n b 2\n c {:foo 1\n ;; :bar 2}]\n ret)", {}) 507 | let s:expectedText = "(let [a 1\n b 2\n c {:foo 1}]\n ;; :bar 2}]\n ret)" 508 | let s:outText = s:result.text 509 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 510 | let s:outText2 = s:result2.text 511 | if s:outText !=# s:expectedText 512 | let s:anyErrorsFound = 1 513 | silent !echo "Indent Mode In/Out test 335 failed" 514 | endif 515 | if s:outText2 !=# s:expectedText 516 | let s:anyErrorsFound = 1 517 | silent !echo "Indent Mode Idempotence test 335 failed" 518 | endif 519 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 520 | let s:outText3 = s:result3.text 521 | if s:outText3 !=# s:expectedText 522 | let s:anyErrorsFound = 1 523 | silent !echo "Indent Mode Cross-mode preservation test 335 failed" 524 | endif 525 | 526 | 527 | "" Test Id: 353 528 | let s:result = g:ParinferLib.IndentMode("(let [a 1 ;; a comment\n ret)", {}) 529 | let s:expectedText = "(let [a 1] ;; a comment\n ret)" 530 | let s:outText = s:result.text 531 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 532 | let s:outText2 = s:result2.text 533 | if s:outText !=# s:expectedText 534 | let s:anyErrorsFound = 1 535 | silent !echo "Indent Mode In/Out test 353 failed" 536 | endif 537 | if s:outText2 !=# s:expectedText 538 | let s:anyErrorsFound = 1 539 | silent !echo "Indent Mode Idempotence test 353 failed" 540 | endif 541 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 542 | let s:outText3 = s:result3.text 543 | if s:outText3 !=# s:expectedText 544 | let s:anyErrorsFound = 1 545 | silent !echo "Indent Mode Cross-mode preservation test 353 failed" 546 | endif 547 | 548 | 549 | "" Test Id: 365 550 | let s:result = g:ParinferLib.IndentMode("; hello \\n world", {}) 551 | let s:expectedText = "; hello \\n world" 552 | let s:outText = s:result.text 553 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 554 | let s:outText2 = s:result2.text 555 | if s:outText !=# s:expectedText 556 | let s:anyErrorsFound = 1 557 | silent !echo "Indent Mode In/Out test 365 failed" 558 | endif 559 | if s:outText2 !=# s:expectedText 560 | let s:anyErrorsFound = 1 561 | silent !echo "Indent Mode Idempotence test 365 failed" 562 | endif 563 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 564 | let s:outText3 = s:result3.text 565 | if s:outText3 !=# s:expectedText 566 | let s:anyErrorsFound = 1 567 | silent !echo "Indent Mode Cross-mode preservation test 365 failed" 568 | endif 569 | 570 | 571 | "" Test Id: 382 572 | let s:result = g:ParinferLib.IndentMode("(def b )", {'cursorX':7,'cursorLine':0,}) 573 | let s:expectedText = "(def b )" 574 | let s:outText = s:result.text 575 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {'cursorX':7,'cursorLine':0,}) 576 | let s:outText2 = s:result2.text 577 | if s:outText !=# s:expectedText 578 | let s:anyErrorsFound = 1 579 | silent !echo "Indent Mode In/Out test 382 failed" 580 | endif 581 | if s:outText2 !=# s:expectedText 582 | let s:anyErrorsFound = 1 583 | silent !echo "Indent Mode Idempotence test 382 failed" 584 | endif 585 | 586 | 587 | "" Test Id: 392 588 | let s:result = g:ParinferLib.IndentMode("(def b )", {}) 589 | let s:expectedText = "(def b)" 590 | let s:outText = s:result.text 591 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 592 | let s:outText2 = s:result2.text 593 | if s:outText !=# s:expectedText 594 | let s:anyErrorsFound = 1 595 | silent !echo "Indent Mode In/Out test 392 failed" 596 | endif 597 | if s:outText2 !=# s:expectedText 598 | let s:anyErrorsFound = 1 599 | silent !echo "Indent Mode Idempotence test 392 failed" 600 | endif 601 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 602 | let s:outText3 = s:result3.text 603 | if s:outText3 !=# s:expectedText 604 | let s:anyErrorsFound = 1 605 | silent !echo "Indent Mode Cross-mode preservation test 392 failed" 606 | endif 607 | 608 | 609 | "" Test Id: 402 610 | let s:result = g:ParinferLib.IndentMode("(def b [[c d] ])", {'cursorX':14,'cursorLine':0,}) 611 | let s:expectedText = "(def b [[c d] ])" 612 | let s:outText = s:result.text 613 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {'cursorX':14,'cursorLine':0,}) 614 | let s:outText2 = s:result2.text 615 | if s:outText !=# s:expectedText 616 | let s:anyErrorsFound = 1 617 | silent !echo "Indent Mode In/Out test 402 failed" 618 | endif 619 | if s:outText2 !=# s:expectedText 620 | let s:anyErrorsFound = 1 621 | silent !echo "Indent Mode Idempotence test 402 failed" 622 | endif 623 | 624 | 625 | "" Test Id: 412 626 | let s:result = g:ParinferLib.IndentMode("(def b [[c d] ])", {}) 627 | let s:expectedText = "(def b [[c d]])" 628 | let s:outText = s:result.text 629 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 630 | let s:outText2 = s:result2.text 631 | if s:outText !=# s:expectedText 632 | let s:anyErrorsFound = 1 633 | silent !echo "Indent Mode In/Out test 412 failed" 634 | endif 635 | if s:outText2 !=# s:expectedText 636 | let s:anyErrorsFound = 1 637 | silent !echo "Indent Mode Idempotence test 412 failed" 638 | endif 639 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 640 | let s:outText3 = s:result3.text 641 | if s:outText3 !=# s:expectedText 642 | let s:anyErrorsFound = 1 643 | silent !echo "Indent Mode Cross-mode preservation test 412 failed" 644 | endif 645 | 646 | 647 | "" Test Id: 423 648 | let s:result = g:ParinferLib.IndentMode("(def b [[c d] ])", {'cursorX':5,'cursorLine':0,}) 649 | let s:expectedText = "(def b [[c d]])" 650 | let s:outText = s:result.text 651 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {'cursorX':5,'cursorLine':0,}) 652 | let s:outText2 = s:result2.text 653 | if s:outText !=# s:expectedText 654 | let s:anyErrorsFound = 1 655 | silent !echo "Indent Mode In/Out test 423 failed" 656 | endif 657 | if s:outText2 !=# s:expectedText 658 | let s:anyErrorsFound = 1 659 | silent !echo "Indent Mode Idempotence test 423 failed" 660 | endif 661 | 662 | 663 | "" Test Id: 437 664 | let s:result = g:ParinferLib.IndentMode("(let [a 1])\n ret)", {}) 665 | let s:expectedText = "(let [a 1]\n ret)" 666 | let s:outText = s:result.text 667 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 668 | let s:outText2 = s:result2.text 669 | if s:outText !=# s:expectedText 670 | let s:anyErrorsFound = 1 671 | silent !echo "Indent Mode In/Out test 437 failed" 672 | endif 673 | if s:outText2 !=# s:expectedText 674 | let s:anyErrorsFound = 1 675 | silent !echo "Indent Mode Idempotence test 437 failed" 676 | endif 677 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 678 | let s:outText3 = s:result3.text 679 | if s:outText3 !=# s:expectedText 680 | let s:anyErrorsFound = 1 681 | silent !echo "Indent Mode Cross-mode preservation test 437 failed" 682 | endif 683 | 684 | 685 | "" Test Id: 449 686 | let s:result = g:ParinferLib.IndentMode("(let [a 1])\n ret)", {'cursorX':11,'cursorLine':0,}) 687 | let s:expectedText = "(let [a 1])\n ret" 688 | let s:outText = s:result.text 689 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {'cursorX':11,'cursorLine':0,}) 690 | let s:outText2 = s:result2.text 691 | if s:outText !=# s:expectedText 692 | let s:anyErrorsFound = 1 693 | silent !echo "Indent Mode In/Out test 449 failed" 694 | endif 695 | if s:outText2 !=# s:expectedText 696 | let s:anyErrorsFound = 1 697 | silent !echo "Indent Mode Idempotence test 449 failed" 698 | endif 699 | 700 | 701 | "" Test Id: 462 702 | let s:result = g:ParinferLib.IndentMode("(let [a 1]) 2\n ret", {}) 703 | let s:expectedText = "(let [a 1]) 2\n ret" 704 | let s:outText = s:result.text 705 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {}) 706 | let s:outText2 = s:result2.text 707 | if s:outText !=# s:expectedText 708 | let s:anyErrorsFound = 1 709 | silent !echo "Indent Mode In/Out test 462 failed" 710 | endif 711 | if s:outText2 !=# s:expectedText 712 | let s:anyErrorsFound = 1 713 | silent !echo "Indent Mode Idempotence test 462 failed" 714 | endif 715 | let s:result3 = g:ParinferLib.ParenMode(s:outText, {}) 716 | let s:outText3 = s:result3.text 717 | if s:outText3 !=# s:expectedText 718 | let s:anyErrorsFound = 1 719 | silent !echo "Indent Mode Cross-mode preservation test 462 failed" 720 | endif 721 | 722 | 723 | "" Test Id: 475 724 | let s:result = g:ParinferLib.IndentMode("(let [a 1])\n ret)", {'cursorX':10,'cursorLine':0,}) 725 | let s:expectedText = "(let [a 1]\n ret)" 726 | let s:outText = s:result.text 727 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {'cursorX':10,'cursorLine':0,}) 728 | let s:outText2 = s:result2.text 729 | if s:outText !=# s:expectedText 730 | let s:anyErrorsFound = 1 731 | silent !echo "Indent Mode In/Out test 475 failed" 732 | endif 733 | if s:outText2 !=# s:expectedText 734 | let s:anyErrorsFound = 1 735 | silent !echo "Indent Mode Idempotence test 475 failed" 736 | endif 737 | 738 | 739 | "" Test Id: 487 740 | let s:result = g:ParinferLib.IndentMode("(let [a 1]) ;\n ret", {'cursorX':13,'cursorLine':0,}) 741 | let s:expectedText = "(let [a 1] ;\n ret)" 742 | let s:outText = s:result.text 743 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {'cursorX':13,'cursorLine':0,}) 744 | let s:outText2 = s:result2.text 745 | if s:outText !=# s:expectedText 746 | let s:anyErrorsFound = 1 747 | silent !echo "Indent Mode In/Out test 487 failed" 748 | endif 749 | if s:outText2 !=# s:expectedText 750 | let s:anyErrorsFound = 1 751 | silent !echo "Indent Mode Idempotence test 487 failed" 752 | endif 753 | 754 | 755 | "" Test Id: 501 756 | let s:result = g:ParinferLib.IndentMode("(let [a 1\n ])", {'cursorX':6,'cursorLine':1,}) 757 | let s:expectedText = "(let [a 1])\n " 758 | let s:outText = s:result.text 759 | let s:result2 = g:ParinferLib.IndentMode(s:outText, {'cursorX':6,'cursorLine':1,}) 760 | let s:outText2 = s:result2.text 761 | if s:outText !=# s:expectedText 762 | let s:anyErrorsFound = 1 763 | silent !echo "Indent Mode In/Out test 501 failed" 764 | endif 765 | if s:outText2 !=# s:expectedText 766 | let s:anyErrorsFound = 1 767 | silent !echo "Indent Mode Idempotence test 501 failed" 768 | endif 769 | 770 | 771 | ""~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 772 | "" Paren Mode Tests 773 | ""~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 774 | 775 | "" Test Id: 4 776 | let s:result = g:ParinferLib.ParenMode("(let [foo 1]\nfoo)", {}) 777 | let s:expectedText = "(let [foo 1]\n foo)" 778 | let s:outText = s:result.text 779 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {}) 780 | let s:outText2 = s:result2.text 781 | if s:outText !=# s:expectedText 782 | let s:anyErrorsFound = 1 783 | silent !echo "Paren Mode In/Out test 4 failed" 784 | endif 785 | if s:outText2 !=# s:expectedText 786 | let s:anyErrorsFound = 1 787 | silent !echo "Paren Mode Idempotence test 4 failed" 788 | endif 789 | let s:result3 = g:ParinferLib.IndentMode(s:outText, {}) 790 | let s:outText3 = s:result3.text 791 | if s:outText3 !=# s:expectedText 792 | let s:anyErrorsFound = 1 793 | silent !echo "Paren Mode Cross-mode preservation test 4 failed" 794 | endif 795 | 796 | 797 | "" Test Id: 16 798 | let s:result = g:ParinferLib.ParenMode("(let [foo 1]\n foo)", {}) 799 | let s:expectedText = "(let [foo 1]\n foo)" 800 | let s:outText = s:result.text 801 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {}) 802 | let s:outText2 = s:result2.text 803 | if s:outText !=# s:expectedText 804 | let s:anyErrorsFound = 1 805 | silent !echo "Paren Mode In/Out test 16 failed" 806 | endif 807 | if s:outText2 !=# s:expectedText 808 | let s:anyErrorsFound = 1 809 | silent !echo "Paren Mode Idempotence test 16 failed" 810 | endif 811 | let s:result3 = g:ParinferLib.IndentMode(s:outText, {}) 812 | let s:outText3 = s:result3.text 813 | if s:outText3 !=# s:expectedText 814 | let s:anyErrorsFound = 1 815 | silent !echo "Paren Mode Cross-mode preservation test 16 failed" 816 | endif 817 | 818 | 819 | "" Test Id: 28 820 | let s:result = g:ParinferLib.ParenMode("(let [foo {:a 1}]\n foo)", {}) 821 | let s:expectedText = "(let [foo {:a 1}]\n foo)" 822 | let s:outText = s:result.text 823 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {}) 824 | let s:outText2 = s:result2.text 825 | if s:outText !=# s:expectedText 826 | let s:anyErrorsFound = 1 827 | silent !echo "Paren Mode In/Out test 28 failed" 828 | endif 829 | if s:outText2 !=# s:expectedText 830 | let s:anyErrorsFound = 1 831 | silent !echo "Paren Mode Idempotence test 28 failed" 832 | endif 833 | let s:result3 = g:ParinferLib.IndentMode(s:outText, {}) 834 | let s:outText3 = s:result3.text 835 | if s:outText3 !=# s:expectedText 836 | let s:anyErrorsFound = 1 837 | silent !echo "Paren Mode Cross-mode preservation test 28 failed" 838 | endif 839 | 840 | 841 | "" Test Id: 40 842 | let s:result = g:ParinferLib.ParenMode("(let [foo [1 2 3]]\n (-> foo\n (map inc)))", {}) 843 | let s:expectedText = "(let [foo [1 2 3]]\n (-> foo\n (map inc)))" 844 | let s:outText = s:result.text 845 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {}) 846 | let s:outText2 = s:result2.text 847 | if s:outText !=# s:expectedText 848 | let s:anyErrorsFound = 1 849 | silent !echo "Paren Mode In/Out test 40 failed" 850 | endif 851 | if s:outText2 !=# s:expectedText 852 | let s:anyErrorsFound = 1 853 | silent !echo "Paren Mode Idempotence test 40 failed" 854 | endif 855 | let s:result3 = g:ParinferLib.IndentMode(s:outText, {}) 856 | let s:outText3 = s:result3.text 857 | if s:outText3 !=# s:expectedText 858 | let s:anyErrorsFound = 1 859 | silent !echo "Paren Mode Cross-mode preservation test 40 failed" 860 | endif 861 | 862 | 863 | "" Test Id: 56 864 | let s:result = g:ParinferLib.ParenMode("(let [foo 1\n bar 2\n\n ] (+ foo bar\n )\n)", {}) 865 | let s:expectedText = "(let [foo 1\n bar 2]\n\n (+ foo bar))\n \n" 866 | let s:outText = s:result.text 867 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {}) 868 | let s:outText2 = s:result2.text 869 | if s:outText !=# s:expectedText 870 | let s:anyErrorsFound = 1 871 | silent !echo "Paren Mode In/Out test 56 failed" 872 | endif 873 | if s:outText2 !=# s:expectedText 874 | let s:anyErrorsFound = 1 875 | silent !echo "Paren Mode Idempotence test 56 failed" 876 | endif 877 | let s:result3 = g:ParinferLib.IndentMode(s:outText, {}) 878 | let s:outText3 = s:result3.text 879 | if s:outText3 !=# s:expectedText 880 | let s:anyErrorsFound = 1 881 | silent !echo "Paren Mode Cross-mode preservation test 56 failed" 882 | endif 883 | 884 | 885 | "" Test Id: 76 886 | let s:result = g:ParinferLib.ParenMode("(def x [1 2 3 4\n 5 6 7 8])", {}) 887 | let s:expectedText = "(def x [1 2 3 4\n 5 6 7 8])" 888 | let s:outText = s:result.text 889 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {}) 890 | let s:outText2 = s:result2.text 891 | if s:outText !=# s:expectedText 892 | let s:anyErrorsFound = 1 893 | silent !echo "Paren Mode In/Out test 76 failed" 894 | endif 895 | if s:outText2 !=# s:expectedText 896 | let s:anyErrorsFound = 1 897 | silent !echo "Paren Mode Idempotence test 76 failed" 898 | endif 899 | let s:result3 = g:ParinferLib.IndentMode(s:outText, {}) 900 | let s:outText3 = s:result3.text 901 | if s:outText3 !=# s:expectedText 902 | let s:anyErrorsFound = 1 903 | silent !echo "Paren Mode Cross-mode preservation test 76 failed" 904 | endif 905 | 906 | 907 | "" Test Id: 88 908 | let s:result = g:ParinferLib.ParenMode(" (assoc x\n:foo 1\n :bar 2)", {}) 909 | let s:expectedText = " (assoc x\n :foo 1\n :bar 2)" 910 | let s:outText = s:result.text 911 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {}) 912 | let s:outText2 = s:result2.text 913 | if s:outText !=# s:expectedText 914 | let s:anyErrorsFound = 1 915 | silent !echo "Paren Mode In/Out test 88 failed" 916 | endif 917 | if s:outText2 !=# s:expectedText 918 | let s:anyErrorsFound = 1 919 | silent !echo "Paren Mode Idempotence test 88 failed" 920 | endif 921 | let s:result3 = g:ParinferLib.IndentMode(s:outText, {}) 922 | let s:outText3 = s:result3.text 923 | if s:outText3 !=# s:expectedText 924 | let s:anyErrorsFound = 1 925 | silent !echo "Paren Mode Cross-mode preservation test 88 failed" 926 | endif 927 | 928 | 929 | "" Test Id: 102 930 | let s:result = g:ParinferLib.ParenMode("(let [foo 1]\n foo)\n\n(let [foo 1]\nfoo)", {}) 931 | let s:expectedText = "(let [foo 1]\n foo)\n\n(let [foo 1]\n foo)" 932 | let s:outText = s:result.text 933 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {}) 934 | let s:outText2 = s:result2.text 935 | if s:outText !=# s:expectedText 936 | let s:anyErrorsFound = 1 937 | silent !echo "Paren Mode In/Out test 102 failed" 938 | endif 939 | if s:outText2 !=# s:expectedText 940 | let s:anyErrorsFound = 1 941 | silent !echo "Paren Mode Idempotence test 102 failed" 942 | endif 943 | let s:result3 = g:ParinferLib.IndentMode(s:outText, {}) 944 | let s:outText3 = s:result3.text 945 | if s:outText3 !=# s:expectedText 946 | let s:anyErrorsFound = 1 947 | silent !echo "Paren Mode Cross-mode preservation test 102 failed" 948 | endif 949 | 950 | 951 | "" Test Id: 120 952 | let s:result = g:ParinferLib.ParenMode("; hello \\n world", {}) 953 | let s:expectedText = "; hello \\n world" 954 | let s:outText = s:result.text 955 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {}) 956 | let s:outText2 = s:result2.text 957 | if s:outText !=# s:expectedText 958 | let s:anyErrorsFound = 1 959 | silent !echo "Paren Mode In/Out test 120 failed" 960 | endif 961 | if s:outText2 !=# s:expectedText 962 | let s:anyErrorsFound = 1 963 | silent !echo "Paren Mode Idempotence test 120 failed" 964 | endif 965 | let s:result3 = g:ParinferLib.IndentMode(s:outText, {}) 966 | let s:outText3 = s:result3.text 967 | if s:outText3 !=# s:expectedText 968 | let s:anyErrorsFound = 1 969 | silent !echo "Paren Mode Cross-mode preservation test 120 failed" 970 | endif 971 | 972 | 973 | "" Test Id: 130 974 | let s:result = g:ParinferLib.ParenMode("(def foo \\,)\n(def bar \\ )", {}) 975 | let s:expectedText = "(def foo \\,)\n(def bar \\ )" 976 | let s:outText = s:result.text 977 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {}) 978 | let s:outText2 = s:result2.text 979 | if s:outText !=# s:expectedText 980 | let s:anyErrorsFound = 1 981 | silent !echo "Paren Mode In/Out test 130 failed" 982 | endif 983 | if s:outText2 !=# s:expectedText 984 | let s:anyErrorsFound = 1 985 | silent !echo "Paren Mode Idempotence test 130 failed" 986 | endif 987 | let s:result3 = g:ParinferLib.IndentMode(s:outText, {}) 988 | let s:outText3 = s:result3.text 989 | if s:outText3 !=# s:expectedText 990 | let s:anyErrorsFound = 1 991 | silent !echo "Paren Mode Cross-mode preservation test 130 failed" 992 | endif 993 | 994 | 995 | "" Test Id: 142 996 | let s:result = g:ParinferLib.ParenMode("(foo [a b\n])", {'cursorX':0,'cursorLine':1,}) 997 | let s:expectedText = "(foo [a b\n ])" 998 | let s:outText = s:result.text 999 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {'cursorX':0,'cursorLine':1,}) 1000 | let s:outText2 = s:result2.text 1001 | if s:outText !=# s:expectedText 1002 | let s:anyErrorsFound = 1 1003 | silent !echo "Paren Mode In/Out test 142 failed" 1004 | endif 1005 | if s:outText2 !=# s:expectedText 1006 | let s:anyErrorsFound = 1 1007 | silent !echo "Paren Mode Idempotence test 142 failed" 1008 | endif 1009 | 1010 | 1011 | "" Test Id: 156 1012 | let s:result = g:ParinferLib.ParenMode("(def foo\n,bar)", {}) 1013 | let s:expectedText = "(def foo\n ,bar)" 1014 | let s:outText = s:result.text 1015 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {}) 1016 | let s:outText2 = s:result2.text 1017 | if s:outText !=# s:expectedText 1018 | let s:anyErrorsFound = 1 1019 | silent !echo "Paren Mode In/Out test 156 failed" 1020 | endif 1021 | if s:outText2 !=# s:expectedText 1022 | let s:anyErrorsFound = 1 1023 | silent !echo "Paren Mode Idempotence test 156 failed" 1024 | endif 1025 | let s:result3 = g:ParinferLib.IndentMode(s:outText, {}) 1026 | let s:outText3 = s:result3.text 1027 | if s:outText3 !=# s:expectedText 1028 | let s:anyErrorsFound = 1 1029 | silent !echo "Paren Mode Cross-mode preservation test 156 failed" 1030 | endif 1031 | 1032 | 1033 | "" Test Id: 168 1034 | let s:result = g:ParinferLib.ParenMode("(def foo [a b]\n ; \"my string\nret)", {}) 1035 | let s:expectedText = "(def foo [a b]\n ; \"my string\nret)" 1036 | let s:outText = s:result.text 1037 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {}) 1038 | let s:outText2 = s:result2.text 1039 | if s:outText !=# s:expectedText 1040 | let s:anyErrorsFound = 1 1041 | silent !echo "Paren Mode In/Out test 168 failed" 1042 | endif 1043 | if s:outText2 !=# s:expectedText 1044 | let s:anyErrorsFound = 1 1045 | silent !echo "Paren Mode Idempotence test 168 failed" 1046 | endif 1047 | let s:result3 = g:ParinferLib.IndentMode(s:outText, {}) 1048 | let s:outText3 = s:result3.text 1049 | if s:outText3 !=# s:expectedText 1050 | let s:anyErrorsFound = 1 1051 | silent !echo "Paren Mode Cross-mode preservation test 168 failed" 1052 | endif 1053 | 1054 | 1055 | "" Test Id: 182 1056 | let s:result = g:ParinferLib.ParenMode("(def foo [a b]\n ; \"my multiline\n ; docstring.\"\nret)", {}) 1057 | let s:expectedText = "(def foo [a b]\n ; \"my multiline\n ; docstring.\"\n ret)" 1058 | let s:outText = s:result.text 1059 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {}) 1060 | let s:outText2 = s:result2.text 1061 | if s:outText !=# s:expectedText 1062 | let s:anyErrorsFound = 1 1063 | silent !echo "Paren Mode In/Out test 182 failed" 1064 | endif 1065 | if s:outText2 !=# s:expectedText 1066 | let s:anyErrorsFound = 1 1067 | silent !echo "Paren Mode Idempotence test 182 failed" 1068 | endif 1069 | let s:result3 = g:ParinferLib.IndentMode(s:outText, {}) 1070 | let s:outText3 = s:result3.text 1071 | if s:outText3 !=# s:expectedText 1072 | let s:anyErrorsFound = 1 1073 | silent !echo "Paren Mode Cross-mode preservation test 182 failed" 1074 | endif 1075 | 1076 | 1077 | "" Test Id: 199 1078 | let s:result = g:ParinferLib.ParenMode("(foo [a b]\\\nc)", {}) 1079 | let s:expectedText = "(foo [a b]\\\nc)" 1080 | let s:outText = s:result.text 1081 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {}) 1082 | let s:outText2 = s:result2.text 1083 | if s:outText !=# s:expectedText 1084 | let s:anyErrorsFound = 1 1085 | silent !echo "Paren Mode In/Out test 199 failed" 1086 | endif 1087 | if s:outText2 !=# s:expectedText 1088 | let s:anyErrorsFound = 1 1089 | silent !echo "Paren Mode Idempotence test 199 failed" 1090 | endif 1091 | let s:result3 = g:ParinferLib.IndentMode(s:outText, {}) 1092 | let s:outText3 = s:result3.text 1093 | if s:outText3 !=# s:expectedText 1094 | let s:anyErrorsFound = 1 1095 | silent !echo "Paren Mode Cross-mode preservation test 199 failed" 1096 | endif 1097 | 1098 | 1099 | "" Test Id: 212 1100 | let s:result = g:ParinferLib.ParenMode("(foo )", {'cursorX':5,'cursorLine':0,}) 1101 | let s:expectedText = "(foo )" 1102 | let s:outText = s:result.text 1103 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {'cursorX':5,'cursorLine':0,}) 1104 | let s:outText2 = s:result2.text 1105 | if s:outText !=# s:expectedText 1106 | let s:anyErrorsFound = 1 1107 | silent !echo "Paren Mode In/Out test 212 failed" 1108 | endif 1109 | if s:outText2 !=# s:expectedText 1110 | let s:anyErrorsFound = 1 1111 | silent !echo "Paren Mode Idempotence test 212 failed" 1112 | endif 1113 | 1114 | 1115 | "" Test Id: 220 1116 | let s:result = g:ParinferLib.ParenMode("(foo [1 2 3 ] )", {'cursorX':12,'cursorLine':0,}) 1117 | let s:expectedText = "(foo [1 2 3 ] )" 1118 | let s:outText = s:result.text 1119 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {'cursorX':12,'cursorLine':0,}) 1120 | let s:outText2 = s:result2.text 1121 | if s:outText !=# s:expectedText 1122 | let s:anyErrorsFound = 1 1123 | silent !echo "Paren Mode In/Out test 220 failed" 1124 | endif 1125 | if s:outText2 !=# s:expectedText 1126 | let s:anyErrorsFound = 1 1127 | silent !echo "Paren Mode Idempotence test 220 failed" 1128 | endif 1129 | 1130 | 1131 | "" Test Id: 230 1132 | let s:result = g:ParinferLib.ParenMode("(foo )", {}) 1133 | let s:expectedText = "(foo)" 1134 | let s:outText = s:result.text 1135 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {}) 1136 | let s:outText2 = s:result2.text 1137 | if s:outText !=# s:expectedText 1138 | let s:anyErrorsFound = 1 1139 | silent !echo "Paren Mode In/Out test 230 failed" 1140 | endif 1141 | if s:outText2 !=# s:expectedText 1142 | let s:anyErrorsFound = 1 1143 | silent !echo "Paren Mode Idempotence test 230 failed" 1144 | endif 1145 | let s:result3 = g:ParinferLib.IndentMode(s:outText, {}) 1146 | let s:outText3 = s:result3.text 1147 | if s:outText3 !=# s:expectedText 1148 | let s:anyErrorsFound = 1 1149 | silent !echo "Paren Mode Cross-mode preservation test 230 failed" 1150 | endif 1151 | 1152 | 1153 | "" Test Id: 238 1154 | let s:result = g:ParinferLib.ParenMode("(foo [1 2 3 ] )", {}) 1155 | let s:expectedText = "(foo [1 2 3])" 1156 | let s:outText = s:result.text 1157 | let s:result2 = g:ParinferLib.ParenMode(s:outText, {}) 1158 | let s:outText2 = s:result2.text 1159 | if s:outText !=# s:expectedText 1160 | let s:anyErrorsFound = 1 1161 | silent !echo "Paren Mode In/Out test 238 failed" 1162 | endif 1163 | if s:outText2 !=# s:expectedText 1164 | let s:anyErrorsFound = 1 1165 | silent !echo "Paren Mode Idempotence test 238 failed" 1166 | endif 1167 | let s:result3 = g:ParinferLib.IndentMode(s:outText, {}) 1168 | let s:outText3 = s:result3.text 1169 | if s:outText3 !=# s:expectedText 1170 | let s:anyErrorsFound = 1 1171 | silent !echo "Paren Mode Cross-mode preservation test 238 failed" 1172 | endif 1173 | 1174 | 1175 | ""~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1176 | "" Show success if there were no failures 1177 | ""~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1178 | if ! s:anyErrorsFound 1179 | silent !echo "All tests passed" 1180 | quit 1181 | else 1182 | cquit 1183 | endif 1184 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # "These are not the test files you are looking for..." 2 | 3 | These test files are copied directly from the [main Parinfer repo]. 4 | 5 | They should not be considered the master source. 6 | 7 | [main Parinfer repo]:https://github.com/shaunlebron/parinfer/tree/master/lib/test/cases 8 | -------------------------------------------------------------------------------- /tests/indent-mode.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "in": { 4 | "fileLineNo": 6, 5 | "lines": [ 6 | "(defn foo", 7 | " [arg", 8 | " ret" 9 | ], 10 | "cursor": null 11 | }, 12 | "out": { 13 | "fileLineNo": 12, 14 | "lines": [ 15 | "(defn foo", 16 | " [arg]", 17 | " ret)" 18 | ], 19 | "cursor": null 20 | } 21 | }, 22 | { 23 | "in": { 24 | "fileLineNo": 20, 25 | "lines": [ 26 | "(defn foo", 27 | " [arg", 28 | " ret" 29 | ], 30 | "cursor": null 31 | }, 32 | "out": { 33 | "fileLineNo": 26, 34 | "lines": [ 35 | "(defn foo", 36 | " [arg", 37 | " ret])" 38 | ], 39 | "cursor": null 40 | } 41 | }, 42 | { 43 | "in": { 44 | "fileLineNo": 34, 45 | "lines": [ 46 | "(defn foo", 47 | "[arg", 48 | " ret" 49 | ], 50 | "cursor": null 51 | }, 52 | "out": { 53 | "fileLineNo": 40, 54 | "lines": [ 55 | "(defn foo)", 56 | "[arg", 57 | " ret]" 58 | ], 59 | "cursor": null 60 | } 61 | }, 62 | { 63 | "in": { 64 | "fileLineNo": 48, 65 | "lines": [ 66 | "(defn foo", 67 | "[arg", 68 | "ret" 69 | ], 70 | "cursor": null 71 | }, 72 | "out": { 73 | "fileLineNo": 54, 74 | "lines": [ 75 | "(defn foo)", 76 | "[arg]", 77 | "ret" 78 | ], 79 | "cursor": null 80 | } 81 | }, 82 | { 83 | "in": { 84 | "fileLineNo": 62, 85 | "lines": [ 86 | "(defn foo", 87 | " [arg", 88 | " ret", 89 | "", 90 | "(defn foo", 91 | " [arg", 92 | " ret" 93 | ], 94 | "cursor": null 95 | }, 96 | "out": { 97 | "fileLineNo": 72, 98 | "lines": [ 99 | "(defn foo", 100 | " [arg]", 101 | " ret)", 102 | "", 103 | "(defn foo", 104 | " [arg]", 105 | " ret)" 106 | ], 107 | "cursor": null 108 | } 109 | }, 110 | { 111 | "in": { 112 | "fileLineNo": 86, 113 | "lines": [ 114 | "(def foo [a b]]" 115 | ], 116 | "cursor": null 117 | }, 118 | "out": { 119 | "fileLineNo": 90, 120 | "lines": [ 121 | "(def foo [a b])" 122 | ], 123 | "cursor": null 124 | } 125 | }, 126 | { 127 | "in": { 128 | "fileLineNo": 96, 129 | "lines": [ 130 | "(let [x {:foo 1 :bar 2]", 131 | " x)" 132 | ], 133 | "cursor": null 134 | }, 135 | "out": { 136 | "fileLineNo": 101, 137 | "lines": [ 138 | "(let [x {:foo 1 :bar 2}]", 139 | " x)" 140 | ], 141 | "cursor": null 142 | } 143 | }, 144 | { 145 | "in": { 146 | "fileLineNo": 110, 147 | "lines": [ 148 | "(def foo \"as" 149 | ], 150 | "cursor": null 151 | }, 152 | "out": { 153 | "fileLineNo": 114, 154 | "lines": [ 155 | "(def foo \"as" 156 | ], 157 | "cursor": null 158 | } 159 | }, 160 | { 161 | "in": { 162 | "fileLineNo": 120, 163 | "lines": [ 164 | "(defn foo [a \"])" 165 | ], 166 | "cursor": null 167 | }, 168 | "out": { 169 | "fileLineNo": 124, 170 | "lines": [ 171 | "(defn foo [a \"])" 172 | ], 173 | "cursor": null 174 | } 175 | }, 176 | { 177 | "in": { 178 | "fileLineNo": 130, 179 | "lines": [ 180 | "(defn foo", 181 | " \"This is docstring.", 182 | " Line 2 here.\"", 183 | " ret" 184 | ], 185 | "cursor": null 186 | }, 187 | "out": { 188 | "fileLineNo": 137, 189 | "lines": [ 190 | "(defn foo", 191 | " \"This is docstring.", 192 | " Line 2 here.\"", 193 | " ret)" 194 | ], 195 | "cursor": null 196 | } 197 | }, 198 | { 199 | "in": { 200 | "fileLineNo": 146, 201 | "lines": [ 202 | "(let [a \"Hello", 203 | "World\"", 204 | " b 2", 205 | " ret" 206 | ], 207 | "cursor": null 208 | }, 209 | "out": { 210 | "fileLineNo": 153, 211 | "lines": [ 212 | "(let [a \"Hello", 213 | "World\"", 214 | " b 2]", 215 | " ret)" 216 | ], 217 | "cursor": null 218 | } 219 | }, 220 | { 221 | "in": { 222 | "fileLineNo": 162, 223 | "lines": [ 224 | "(let [a \"])\"", 225 | " b 2" 226 | ], 227 | "cursor": null 228 | }, 229 | "out": { 230 | "fileLineNo": 167, 231 | "lines": [ 232 | "(let [a \"])\"", 233 | " b 2])" 234 | ], 235 | "cursor": null 236 | } 237 | }, 238 | { 239 | "in": { 240 | "fileLineNo": 174, 241 | "lines": [ 242 | "(def foo \"\\\"\"" 243 | ], 244 | "cursor": null 245 | }, 246 | "out": { 247 | "fileLineNo": 178, 248 | "lines": [ 249 | "(def foo \"\\\"\")" 250 | ], 251 | "cursor": null 252 | } 253 | }, 254 | { 255 | "in": { 256 | "fileLineNo": 191, 257 | "lines": [ 258 | "\"\"]\"" 259 | ], 260 | "cursor": { 261 | "cursorX": 1, 262 | "cursorLine": 0 263 | } 264 | }, 265 | "out": { 266 | "fileLineNo": 195, 267 | "lines": [ 268 | "\"\"]\"" 269 | ], 270 | "cursor": null 271 | } 272 | }, 273 | { 274 | "in": { 275 | "fileLineNo": 201, 276 | "lines": [ 277 | "(def foo", 278 | " \"", 279 | " \"(a b)", 280 | " c\")" 281 | ], 282 | "cursor": { 283 | "cursorX": 3, 284 | "cursorLine": 1 285 | } 286 | }, 287 | "out": { 288 | "fileLineNo": 208, 289 | "lines": [ 290 | "(def foo", 291 | " \"", 292 | " \"(a b)", 293 | " c\")" 294 | ], 295 | "cursor": null 296 | } 297 | }, 298 | { 299 | "in": { 300 | "fileLineNo": 224, 301 | "lines": [ 302 | "(for [col columns]", 303 | " \"", 304 | " [:div.td {:style \"max-width: 500px;\"}])" 305 | ], 306 | "cursor": { 307 | "cursorX": 3, 308 | "cursorLine": 1 309 | } 310 | }, 311 | "out": { 312 | "fileLineNo": 230, 313 | "lines": [ 314 | "(for [col columns]", 315 | " \"", 316 | " [:div.td {:style \"max-width: 500px;\"}])" 317 | ], 318 | "cursor": null 319 | } 320 | }, 321 | { 322 | "in": { 323 | "fileLineNo": 240, 324 | "lines": [ 325 | "(def foo [a b]", 326 | " ; \"my multiline", 327 | " ; docstring.\"", 328 | "ret)" 329 | ], 330 | "cursor": null 331 | }, 332 | "out": { 333 | "fileLineNo": 247, 334 | "lines": [ 335 | "(def foo [a b])", 336 | " ; \"my multiline", 337 | " ; docstring.\"", 338 | "ret" 339 | ], 340 | "cursor": null 341 | } 342 | }, 343 | { 344 | "in": { 345 | "fileLineNo": 256, 346 | "lines": [ 347 | "(def foo [a b]", 348 | " ; \"\"\\\"", 349 | "ret)" 350 | ], 351 | "cursor": null 352 | }, 353 | "out": { 354 | "fileLineNo": 262, 355 | "lines": [ 356 | "(def foo [a b])", 357 | " ; \"\"\\\"", 358 | "ret" 359 | ], 360 | "cursor": null 361 | } 362 | }, 363 | { 364 | "in": { 365 | "fileLineNo": 272, 366 | "lines": [ 367 | "(defn foo [a b", 368 | " \\[", 369 | " ret" 370 | ], 371 | "cursor": null 372 | }, 373 | "out": { 374 | "fileLineNo": 278, 375 | "lines": [ 376 | "(defn foo [a b]", 377 | " \\[", 378 | " ret)" 379 | ], 380 | "cursor": null 381 | } 382 | }, 383 | { 384 | "in": { 385 | "fileLineNo": 287, 386 | "lines": [ 387 | "(def foo \\;" 388 | ], 389 | "cursor": null 390 | }, 391 | "out": { 392 | "fileLineNo": 291, 393 | "lines": [ 394 | "(def foo \\;)" 395 | ], 396 | "cursor": null 397 | } 398 | }, 399 | { 400 | "in": { 401 | "fileLineNo": 297, 402 | "lines": [ 403 | "(def foo \\,", 404 | "(def bar \\ " 405 | ], 406 | "cursor": null 407 | }, 408 | "out": { 409 | "fileLineNo": 302, 410 | "lines": [ 411 | "(def foo \\,)", 412 | "(def bar \\ )" 413 | ], 414 | "cursor": null 415 | } 416 | }, 417 | { 418 | "in": { 419 | "fileLineNo": 309, 420 | "lines": [ 421 | "(foo [a b\\", 422 | " c)" 423 | ], 424 | "cursor": null 425 | }, 426 | "out": { 427 | "fileLineNo": 314, 428 | "lines": [ 429 | "(foo [a b\\", 430 | " c)" 431 | ], 432 | "cursor": null 433 | } 434 | }, 435 | { 436 | "in": { 437 | "fileLineNo": 324, 438 | "lines": [ 439 | "(def foo ;)" 440 | ], 441 | "cursor": null 442 | }, 443 | "out": { 444 | "fileLineNo": 328, 445 | "lines": [ 446 | "(def foo) ;)" 447 | ], 448 | "cursor": null 449 | } 450 | }, 451 | { 452 | "in": { 453 | "fileLineNo": 335, 454 | "lines": [ 455 | "(let [a 1", 456 | " b 2", 457 | " c {:foo 1", 458 | " ;; :bar 2}]", 459 | " ret)" 460 | ], 461 | "cursor": null 462 | }, 463 | "out": { 464 | "fileLineNo": 343, 465 | "lines": [ 466 | "(let [a 1", 467 | " b 2", 468 | " c {:foo 1}]", 469 | " ;; :bar 2}]", 470 | " ret)" 471 | ], 472 | "cursor": null 473 | } 474 | }, 475 | { 476 | "in": { 477 | "fileLineNo": 353, 478 | "lines": [ 479 | "(let [a 1 ;; a comment", 480 | " ret)" 481 | ], 482 | "cursor": null 483 | }, 484 | "out": { 485 | "fileLineNo": 358, 486 | "lines": [ 487 | "(let [a 1] ;; a comment", 488 | " ret)" 489 | ], 490 | "cursor": null 491 | } 492 | }, 493 | { 494 | "in": { 495 | "fileLineNo": 365, 496 | "lines": [ 497 | "; hello \\n world" 498 | ], 499 | "cursor": null 500 | }, 501 | "out": { 502 | "fileLineNo": 369, 503 | "lines": [ 504 | "; hello \\n world" 505 | ], 506 | "cursor": null 507 | } 508 | }, 509 | { 510 | "in": { 511 | "fileLineNo": 382, 512 | "lines": [ 513 | "(def b )" 514 | ], 515 | "cursor": { 516 | "cursorX": 7, 517 | "cursorLine": 0 518 | } 519 | }, 520 | "out": { 521 | "fileLineNo": 386, 522 | "lines": [ 523 | "(def b )" 524 | ], 525 | "cursor": null 526 | } 527 | }, 528 | { 529 | "in": { 530 | "fileLineNo": 392, 531 | "lines": [ 532 | "(def b )" 533 | ], 534 | "cursor": null 535 | }, 536 | "out": { 537 | "fileLineNo": 396, 538 | "lines": [ 539 | "(def b)" 540 | ], 541 | "cursor": null 542 | } 543 | }, 544 | { 545 | "in": { 546 | "fileLineNo": 402, 547 | "lines": [ 548 | "(def b [[c d] ])" 549 | ], 550 | "cursor": { 551 | "cursorX": 14, 552 | "cursorLine": 0 553 | } 554 | }, 555 | "out": { 556 | "fileLineNo": 406, 557 | "lines": [ 558 | "(def b [[c d] ])" 559 | ], 560 | "cursor": null 561 | } 562 | }, 563 | { 564 | "in": { 565 | "fileLineNo": 412, 566 | "lines": [ 567 | "(def b [[c d] ])" 568 | ], 569 | "cursor": null 570 | }, 571 | "out": { 572 | "fileLineNo": 416, 573 | "lines": [ 574 | "(def b [[c d]])" 575 | ], 576 | "cursor": null 577 | } 578 | }, 579 | { 580 | "in": { 581 | "fileLineNo": 423, 582 | "lines": [ 583 | "(def b [[c d] ])" 584 | ], 585 | "cursor": { 586 | "cursorX": 5, 587 | "cursorLine": 0 588 | } 589 | }, 590 | "out": { 591 | "fileLineNo": 427, 592 | "lines": [ 593 | "(def b [[c d]])" 594 | ], 595 | "cursor": null 596 | } 597 | }, 598 | { 599 | "in": { 600 | "fileLineNo": 437, 601 | "lines": [ 602 | "(let [a 1])", 603 | " ret)" 604 | ], 605 | "cursor": null 606 | }, 607 | "out": { 608 | "fileLineNo": 442, 609 | "lines": [ 610 | "(let [a 1]", 611 | " ret)" 612 | ], 613 | "cursor": null 614 | } 615 | }, 616 | { 617 | "in": { 618 | "fileLineNo": 449, 619 | "lines": [ 620 | "(let [a 1])", 621 | " ret)" 622 | ], 623 | "cursor": { 624 | "cursorX": 11, 625 | "cursorLine": 0 626 | } 627 | }, 628 | "out": { 629 | "fileLineNo": 454, 630 | "lines": [ 631 | "(let [a 1])", 632 | " ret" 633 | ], 634 | "cursor": null 635 | } 636 | }, 637 | { 638 | "in": { 639 | "fileLineNo": 462, 640 | "lines": [ 641 | "(let [a 1]) 2", 642 | " ret" 643 | ], 644 | "cursor": null 645 | }, 646 | "out": { 647 | "fileLineNo": 467, 648 | "lines": [ 649 | "(let [a 1]) 2", 650 | " ret" 651 | ], 652 | "cursor": null 653 | } 654 | }, 655 | { 656 | "in": { 657 | "fileLineNo": 475, 658 | "lines": [ 659 | "(let [a 1])", 660 | " ret)" 661 | ], 662 | "cursor": { 663 | "cursorX": 10, 664 | "cursorLine": 0 665 | } 666 | }, 667 | "out": { 668 | "fileLineNo": 480, 669 | "lines": [ 670 | "(let [a 1]", 671 | " ret)" 672 | ], 673 | "cursor": null 674 | } 675 | }, 676 | { 677 | "in": { 678 | "fileLineNo": 487, 679 | "lines": [ 680 | "(let [a 1]) ;", 681 | " ret" 682 | ], 683 | "cursor": { 684 | "cursorX": 13, 685 | "cursorLine": 0 686 | } 687 | }, 688 | "out": { 689 | "fileLineNo": 492, 690 | "lines": [ 691 | "(let [a 1] ;", 692 | " ret)" 693 | ], 694 | "cursor": null 695 | } 696 | }, 697 | { 698 | "in": { 699 | "fileLineNo": 501, 700 | "lines": [ 701 | "(let [a 1", 702 | " ])" 703 | ], 704 | "cursor": { 705 | "cursorX": 6, 706 | "cursorLine": 1 707 | } 708 | }, 709 | "out": { 710 | "fileLineNo": 506, 711 | "lines": [ 712 | "(let [a 1])", 713 | " " 714 | ], 715 | "cursor": null 716 | } 717 | } 718 | ] 719 | -------------------------------------------------------------------------------- /tests/paren-mode.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "in": { 4 | "fileLineNo": 4, 5 | "lines": [ 6 | "(let [foo 1]", 7 | "foo)" 8 | ], 9 | "cursor": null 10 | }, 11 | "out": { 12 | "fileLineNo": 9, 13 | "lines": [ 14 | "(let [foo 1]", 15 | " foo)" 16 | ], 17 | "cursor": null 18 | } 19 | }, 20 | { 21 | "in": { 22 | "fileLineNo": 16, 23 | "lines": [ 24 | "(let [foo 1]", 25 | " foo)" 26 | ], 27 | "cursor": null 28 | }, 29 | "out": { 30 | "fileLineNo": 21, 31 | "lines": [ 32 | "(let [foo 1]", 33 | " foo)" 34 | ], 35 | "cursor": null 36 | } 37 | }, 38 | { 39 | "in": { 40 | "fileLineNo": 28, 41 | "lines": [ 42 | "(let [foo {:a 1}]", 43 | " foo)" 44 | ], 45 | "cursor": null 46 | }, 47 | "out": { 48 | "fileLineNo": 33, 49 | "lines": [ 50 | "(let [foo {:a 1}]", 51 | " foo)" 52 | ], 53 | "cursor": null 54 | } 55 | }, 56 | { 57 | "in": { 58 | "fileLineNo": 40, 59 | "lines": [ 60 | "(let [foo [1 2 3]]", 61 | " (-> foo", 62 | " (map inc)))" 63 | ], 64 | "cursor": null 65 | }, 66 | "out": { 67 | "fileLineNo": 46, 68 | "lines": [ 69 | "(let [foo [1 2 3]]", 70 | " (-> foo", 71 | " (map inc)))" 72 | ], 73 | "cursor": null 74 | } 75 | }, 76 | { 77 | "in": { 78 | "fileLineNo": 56, 79 | "lines": [ 80 | "(let [foo 1", 81 | " bar 2", 82 | "", 83 | " ] (+ foo bar", 84 | " )", 85 | ")" 86 | ], 87 | "cursor": null 88 | }, 89 | "out": { 90 | "fileLineNo": 65, 91 | "lines": [ 92 | "(let [foo 1", 93 | " bar 2]", 94 | "", 95 | " (+ foo bar))", 96 | " ", 97 | "" 98 | ], 99 | "cursor": null 100 | } 101 | }, 102 | { 103 | "in": { 104 | "fileLineNo": 76, 105 | "lines": [ 106 | "(def x [1 2 3 4", 107 | " 5 6 7 8])" 108 | ], 109 | "cursor": null 110 | }, 111 | "out": { 112 | "fileLineNo": 81, 113 | "lines": [ 114 | "(def x [1 2 3 4", 115 | " 5 6 7 8])" 116 | ], 117 | "cursor": null 118 | } 119 | }, 120 | { 121 | "in": { 122 | "fileLineNo": 88, 123 | "lines": [ 124 | " (assoc x", 125 | ":foo 1", 126 | " :bar 2)" 127 | ], 128 | "cursor": null 129 | }, 130 | "out": { 131 | "fileLineNo": 94, 132 | "lines": [ 133 | " (assoc x", 134 | " :foo 1", 135 | " :bar 2)" 136 | ], 137 | "cursor": null 138 | } 139 | }, 140 | { 141 | "in": { 142 | "fileLineNo": 102, 143 | "lines": [ 144 | "(let [foo 1]", 145 | " foo)", 146 | "", 147 | "(let [foo 1]", 148 | "foo)" 149 | ], 150 | "cursor": null 151 | }, 152 | "out": { 153 | "fileLineNo": 110, 154 | "lines": [ 155 | "(let [foo 1]", 156 | " foo)", 157 | "", 158 | "(let [foo 1]", 159 | " foo)" 160 | ], 161 | "cursor": null 162 | } 163 | }, 164 | { 165 | "in": { 166 | "fileLineNo": 120, 167 | "lines": [ 168 | "; hello \\n world" 169 | ], 170 | "cursor": null 171 | }, 172 | "out": { 173 | "fileLineNo": 124, 174 | "lines": [ 175 | "; hello \\n world" 176 | ], 177 | "cursor": null 178 | } 179 | }, 180 | { 181 | "in": { 182 | "fileLineNo": 130, 183 | "lines": [ 184 | "(def foo \\,)", 185 | "(def bar \\ )" 186 | ], 187 | "cursor": null 188 | }, 189 | "out": { 190 | "fileLineNo": 135, 191 | "lines": [ 192 | "(def foo \\,)", 193 | "(def bar \\ )" 194 | ], 195 | "cursor": null 196 | } 197 | }, 198 | { 199 | "in": { 200 | "fileLineNo": 142, 201 | "lines": [ 202 | "(foo [a b", 203 | "])" 204 | ], 205 | "cursor": { 206 | "cursorX": 0, 207 | "cursorLine": 1 208 | } 209 | }, 210 | "out": { 211 | "fileLineNo": 147, 212 | "lines": [ 213 | "(foo [a b", 214 | " ])" 215 | ], 216 | "cursor": null 217 | } 218 | }, 219 | { 220 | "in": { 221 | "fileLineNo": 156, 222 | "lines": [ 223 | "(def foo", 224 | ",bar)" 225 | ], 226 | "cursor": null 227 | }, 228 | "out": { 229 | "fileLineNo": 161, 230 | "lines": [ 231 | "(def foo", 232 | " ,bar)" 233 | ], 234 | "cursor": null 235 | } 236 | }, 237 | { 238 | "in": { 239 | "fileLineNo": 168, 240 | "lines": [ 241 | "(def foo [a b]", 242 | " ; \"my string", 243 | "ret)" 244 | ], 245 | "cursor": null 246 | }, 247 | "out": { 248 | "fileLineNo": 174, 249 | "lines": [ 250 | "(def foo [a b]", 251 | " ; \"my string", 252 | "ret)" 253 | ], 254 | "cursor": null 255 | } 256 | }, 257 | { 258 | "in": { 259 | "fileLineNo": 182, 260 | "lines": [ 261 | "(def foo [a b]", 262 | " ; \"my multiline", 263 | " ; docstring.\"", 264 | "ret)" 265 | ], 266 | "cursor": null 267 | }, 268 | "out": { 269 | "fileLineNo": 189, 270 | "lines": [ 271 | "(def foo [a b]", 272 | " ; \"my multiline", 273 | " ; docstring.\"", 274 | " ret)" 275 | ], 276 | "cursor": null 277 | } 278 | }, 279 | { 280 | "in": { 281 | "fileLineNo": 199, 282 | "lines": [ 283 | "(foo [a b]\\", 284 | "c)" 285 | ], 286 | "cursor": null 287 | }, 288 | "out": { 289 | "fileLineNo": 204, 290 | "lines": [ 291 | "(foo [a b]\\", 292 | "c)" 293 | ], 294 | "cursor": null 295 | } 296 | }, 297 | { 298 | "in": { 299 | "fileLineNo": 212, 300 | "lines": [ 301 | "(foo )" 302 | ], 303 | "cursor": { 304 | "cursorX": 5, 305 | "cursorLine": 0 306 | } 307 | }, 308 | "out": { 309 | "fileLineNo": 216, 310 | "lines": [ 311 | "(foo )" 312 | ], 313 | "cursor": null 314 | } 315 | }, 316 | { 317 | "in": { 318 | "fileLineNo": 220, 319 | "lines": [ 320 | "(foo [1 2 3 ] )" 321 | ], 322 | "cursor": { 323 | "cursorX": 12, 324 | "cursorLine": 0 325 | } 326 | }, 327 | "out": { 328 | "fileLineNo": 224, 329 | "lines": [ 330 | "(foo [1 2 3 ] )" 331 | ], 332 | "cursor": null 333 | } 334 | }, 335 | { 336 | "in": { 337 | "fileLineNo": 230, 338 | "lines": [ 339 | "(foo )" 340 | ], 341 | "cursor": null 342 | }, 343 | "out": { 344 | "fileLineNo": 234, 345 | "lines": [ 346 | "(foo)" 347 | ], 348 | "cursor": null 349 | } 350 | }, 351 | { 352 | "in": { 353 | "fileLineNo": 238, 354 | "lines": [ 355 | "(foo [1 2 3 ] )" 356 | ], 357 | "cursor": null 358 | }, 359 | "out": { 360 | "fileLineNo": 242, 361 | "lines": [ 362 | "(foo [1 2 3])" 363 | ], 364 | "cursor": null 365 | } 366 | } 367 | ] -------------------------------------------------------------------------------- /tests/really_long_file: -------------------------------------------------------------------------------- 1 | ;; Parinfer NOTE: this is just a large file taken from ClojureScript 2 | ;; which we use to test the speed of Parinfer. 3 | 4 | ; Copyright (c) Rich Hickey. All rights reserved. 5 | ; The use and distribution terms for this software are covered by the 6 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 7 | ; which can be found in the file epl-v10.html at the root of this distribution. 8 | ; By using this software in any fashion, you are agreeing to be bound by 9 | ; the terms of this license. 10 | ; You must not remove this notice, or any other, from this software. 11 | 12 | (ns cljs.analyzer 13 | #?(:clj (:refer-clojure :exclude [macroexpand-1 ensure]) 14 | :cljs (:refer-clojure :exclude [macroexpand-1 ns-interns ensure js-reserved])) 15 | #?(:cljs (:require-macros 16 | [cljs.analyzer.macros 17 | :refer [no-warn wrapping-errors 18 | disallowing-recur allowing-redef]] 19 | [cljs.env.macros :refer [ensure]])) 20 | #?(:clj (:require [cljs.util :as util :refer [ns->relpath topo-sort]] 21 | [clojure.java.io :as io] 22 | [clojure.string :as string] 23 | [clojure.set :as set] 24 | [cljs.env :as env :refer [ensure]] 25 | [cljs.js-deps :as deps] 26 | [cljs.tagged-literals :as tags] 27 | [clojure.tools.reader :as reader] 28 | [clojure.tools.reader.reader-types :as readers] 29 | [clojure.edn :as edn]) 30 | :cljs (:require [goog.string :as gstring] 31 | [clojure.string :as string] 32 | [clojure.set :as set] 33 | [cljs.env :as env] 34 | [cljs.tagged-literals :as tags] 35 | [cljs.tools.reader :as reader] 36 | [cljs.tools.reader.reader-types :as readers] 37 | [cljs.reader :as edn])) 38 | #?(:clj (:import [java.io File Reader PushbackReader] 39 | [java.net URL] 40 | [clojure.lang Namespace Var LazySeq] 41 | [cljs.tagged_literals JSValue]))) 42 | 43 | #?(:clj (set! *warn-on-reflection* true)) 44 | 45 | (def ^:dynamic *cljs-ns* 'cljs.user) 46 | (def ^:dynamic *cljs-file* nil) 47 | #?(:clj (def ^:dynamic *unchecked-if* (atom false))) 48 | (def ^:dynamic *cljs-static-fns* false) 49 | (def ^:dynamic *cljs-macros-path* "/cljs/core") 50 | (def ^:dynamic *cljs-macros-is-classpath* true) 51 | (def ^:dynamic *cljs-dep-set* (with-meta #{} {:dep-path []})) 52 | (def ^:dynamic *analyze-deps* true) 53 | (def ^:dynamic *load-tests* true) 54 | (def ^:dynamic *load-macros* true) 55 | (def ^:dynamic *reload-macros* false) 56 | (def ^:dynamic *macro-infer* true) 57 | 58 | (def ^:dynamic *file-defs* nil) 59 | 60 | ;; log compiler activities 61 | (def ^:dynamic *verbose* false) 62 | 63 | (def -cljs-macros-loaded (atom false)) 64 | 65 | (def ^:dynamic *cljs-warnings* 66 | {:preamble-missing true 67 | :unprovided true 68 | :undeclared-var true 69 | :undeclared-ns true 70 | :undeclared-ns-form true 71 | :redef true 72 | :redef-in-file true 73 | :dynamic true 74 | :fn-var true 75 | :fn-arity true 76 | :fn-deprecated true 77 | :protocol-deprecated true 78 | :undeclared-protocol-symbol true 79 | :invalid-protocol-symbol true 80 | :multiple-variadic-overloads true 81 | :variadic-max-arity true 82 | :overload-arity true 83 | :extending-base-js-type true 84 | :invoke-ctor true 85 | :invalid-arithmetic true 86 | :protocol-invalid-method true 87 | :protocol-duped-method true 88 | :protocol-multiple-impls true 89 | :protocol-with-variadic-method true 90 | :single-segment-namespace true 91 | :munged-namespace true 92 | :ns-var-clash true 93 | :extend-type-invalid-method-shape true 94 | :unsupported-js-module-type true 95 | :unsupported-preprocess-value true}) 96 | 97 | (def js-reserved 98 | #{"arguments" "abstract" "boolean" "break" "byte" "case" 99 | "catch" "char" "class" "const" "continue" 100 | "debugger" "default" "delete" "do" "double" 101 | "else" "enum" "export" "extends" "final" 102 | "finally" "float" "for" "function" "goto" "if" 103 | "implements" "import" "in" "instanceof" "int" 104 | "interface" "let" "long" "native" "new" 105 | "package" "private" "protected" "public" 106 | "return" "short" "static" "super" "switch" 107 | "synchronized" "this" "throw" "throws" 108 | "transient" "try" "typeof" "var" "void" 109 | "volatile" "while" "with" "yield" "methods" 110 | "null" "constructor"}) 111 | 112 | #?(:clj (def SENTINEL (Object.)) 113 | :cljs (def SENTINEL (js-obj))) 114 | 115 | (defn gets 116 | ([m k0 k1] 117 | (let [m (get m k0 SENTINEL)] 118 | (when-not (identical? m SENTINEL) 119 | (get m k1)))) 120 | ([m k0 k1 k2] 121 | (let [m (get m k0 SENTINEL)] 122 | (when-not (identical? m SENTINEL) 123 | (let [m (get m k1 SENTINEL)] 124 | (when-not (identical? m SENTINEL) 125 | (get m k2)))))) 126 | ([m k0 k1 k2 k3] 127 | (let [m (get m k0 SENTINEL)] 128 | (when-not (identical? m SENTINEL) 129 | (let [m (get m k1 SENTINEL)] 130 | (when-not (identical? m SENTINEL) 131 | (let [m (get m k2 SENTINEL)] 132 | (when-not (identical? m SENTINEL) 133 | (get m k3))))))))) 134 | 135 | #?(:cljs 136 | (def CLJ_NIL_SYM 'clj-nil)) 137 | 138 | #?(:cljs 139 | (def NUMBER_SYM 'number)) 140 | 141 | #?(:cljs 142 | (def STRING_SYM 'string)) 143 | 144 | (def BOOLEAN_SYM 'boolean) 145 | 146 | #?(:cljs 147 | (def JS_STAR_SYM 'js*)) 148 | 149 | #?(:cljs 150 | (def DOT_SYM '.)) 151 | 152 | #?(:cljs 153 | (def NEW_SYM 'new)) 154 | 155 | #?(:cljs 156 | (def CLJS_CORE_SYM 'cljs.core)) 157 | 158 | #?(:cljs 159 | (def CLJS_CORE_MACROS_SYM 'cljs.core$macros)) 160 | 161 | (def IGNORE_SYM 'ignore) 162 | 163 | (def ANY_SYM 'any) 164 | 165 | #?(:cljs 166 | (defn ^boolean cljs-seq? [x] 167 | (implements? ISeq x))) 168 | 169 | #?(:cljs 170 | (defn ^boolean cljs-map? [x] 171 | (implements? IMap x))) 172 | 173 | #?(:cljs 174 | (defn ^boolean cljs-vector? [x] 175 | (implements? IVector x))) 176 | 177 | #?(:cljs 178 | (defn ^boolean cljs-set? [x] 179 | (implements? ISet x))) 180 | 181 | #?(:cljs 182 | (defn munge-path [ss] 183 | (munge (str ss)))) 184 | 185 | #?(:cljs 186 | (defn ns->relpath 187 | "Given a namespace as a symbol return the relative path. May optionally 188 | provide the file extension, defaults to :cljs." 189 | ([ns] (ns->relpath ns :cljs)) 190 | ([ns ext] 191 | (str (string/replace (munge-path ns) \. \/) "." (name ext))))) 192 | 193 | #?(:cljs 194 | (defn topo-sort 195 | ([x get-deps] 196 | (topo-sort x 0 (atom (sorted-map)) (memoize get-deps))) 197 | ([x depth state memo-get-deps] 198 | (let [deps (memo-get-deps x)] 199 | (swap! state update-in [depth] (fnil into #{}) deps) 200 | (doseq [dep deps] 201 | (topo-sort dep (inc depth) state memo-get-deps)) 202 | (doseq [[relpath ns-sym :cljs) 232 | ", " (ns->relpath ns-sym :cljc) 233 | ", or Closure namespace \"" js-provide "\"")) 234 | 235 | (defmethod error-message :undeclared-macros-ns 236 | [warning-type {:keys [ns-sym js-provide] :as info}] 237 | (str "No such macros namespace: " ns-sym 238 | ", could not locate " (ns->relpath ns-sym :clj) 239 | " or " (ns->relpath ns-sym :cljc))) 240 | 241 | (defmethod error-message :dynamic 242 | [warning-type info] 243 | (str (:name info) " not declared ^:dynamic")) 244 | 245 | (defmethod error-message :redef 246 | [warning-type info] 247 | (str (:sym info) " already refers to: " (symbol (str (:ns info)) (str (:sym info))) 248 | " being replaced by: " (symbol (str (:ns-name info)) (str (:sym info))))) 249 | 250 | (defmethod error-message :redef-in-file 251 | [warning-type info] 252 | (str (:sym info) " at line " (:line info) " is being replaced")) 253 | 254 | (defmethod error-message :fn-var 255 | [warning-type info] 256 | (str (symbol (str (:ns-name info)) (str (:sym info))) 257 | " no longer fn, references are stale")) 258 | 259 | (defmethod error-message :fn-arity 260 | [warning-type info] 261 | (str "Wrong number of args (" (:argc info) ") passed to " 262 | (or (:ctor info) 263 | (:name info)))) 264 | 265 | (defmethod error-message :fn-deprecated 266 | [warning-type info] 267 | (str (-> info :fexpr :info :name) " is deprecated.")) 268 | 269 | (defmethod error-message :undeclared-ns-form 270 | [warning-type info] 271 | (str "Referred " (:type info) " " (:lib info) "/" (:sym info) " does not exist")) 272 | 273 | (defmethod error-message :protocol-deprecated 274 | [warning-type info] 275 | (str "Protocol " (:protocol info) " is deprecated")) 276 | 277 | (defmethod error-message :undeclared-protocol-symbol 278 | [warning-type info] 279 | (str "Can't resolve protocol symbol " (:protocol info))) 280 | 281 | (defmethod error-message :invalid-protocol-symbol 282 | [warning-type info] 283 | (str "Symbol " (:protocol info) " is not a protocol")) 284 | 285 | (defmethod error-message :protocol-invalid-method 286 | [warning-type info] 287 | (if (:no-such-method info) 288 | (str "Bad method signature in protocol implementation, " 289 | (:protocol info) " does not declare method called " (:fname info)) 290 | (str "Bad method signature in protocol implementation, " 291 | (:protocol info) " " (:fname info) " does not declare arity " (:invalid-arity info)))) 292 | 293 | (defmethod error-message :protocol-duped-method 294 | [warning-type info] 295 | (str "Duplicated methods in protocol implementation " (:protocol info) " " (:fname info))) 296 | 297 | (defmethod error-message :protocol-multiple-impls 298 | [warning-type info] 299 | (str "Protocol " (:protocol info) " implemented multiple times")) 300 | 301 | (defmethod error-message :protocol-with-variadic-method 302 | [warning-type info] 303 | (str "Protocol " (:protocol info) " declares method " 304 | (:name info) " with variadic signature (&)")) 305 | 306 | (defmethod error-message :multiple-variadic-overloads 307 | [warning-type info] 308 | (str (:name info) ": Can't have more than 1 variadic overload")) 309 | 310 | (defmethod error-message :variadic-max-arity 311 | [warning-type info] 312 | (str (:name info) ": Can't have fixed arity function with more params than variadic function")) 313 | 314 | (defmethod error-message :overload-arity 315 | [warning-type info] 316 | (str (:name info) ": Can't have 2 overloads with same arity")) 317 | 318 | (defmethod error-message :extending-base-js-type 319 | [warning-type info] 320 | (str "Extending an existing JavaScript type - use a different symbol name " 321 | "instead of " (:current-symbol info) " e.g " (:suggested-symbol info))) 322 | 323 | (defmethod error-message :invalid-arithmetic 324 | [warning-type info] 325 | (str (:js-op info) ", all arguments must be numbers, got " (:types info) " instead.")) 326 | 327 | (defmethod error-message :invoke-ctor 328 | [warning-type info] 329 | (str "Cannot invoke type constructor " (-> info :fexpr :info :name) " as function ")) 330 | 331 | (defmethod error-message :single-segment-namespace 332 | [warning-type info] 333 | (str (:name info) " is a single segment namespace")) 334 | 335 | (defmethod error-message :munged-namespace 336 | [warning-type {:keys [name] :as info}] 337 | (let [munged (->> (string/split (clojure.core/name name) #"\.") 338 | (map #(if (js-reserved %) (str % "$") %)) 339 | (string/join ".") 340 | (munge))] 341 | (str "Namespace " name " contains a reserved JavaScript keyword," 342 | " the corresponding Google Closure namespace will be munged to " munged))) 343 | 344 | (defmethod error-message :ns-var-clash 345 | [warning-type {:keys [ns var] :as info}] 346 | (str "Namespace " ns " clashes with var " var)) 347 | 348 | (defmethod error-message :extend-type-invalid-method-shape 349 | [warning-type {:keys [protocol method] :as info}] 350 | (str "Bad extend-type method shape for protocol " protocol " method " method 351 | ", method arities must be grouped together")) 352 | 353 | (defmethod error-message :unsupported-js-module-type 354 | [warning-type {:keys [module-type file] :as info}] 355 | (str "Unsupported JavaScript module type " module-type " for foreign library " 356 | file ".")) 357 | 358 | (defmethod error-message :unsupported-preprocess-value 359 | [warning-type {:keys [preprocess file]}] 360 | (str "Unsupported preprocess value " preprocess " for foreign library " 361 | file ".")) 362 | 363 | (defn default-warning-handler [warning-type env extra] 364 | (when (warning-type *cljs-warnings*) 365 | (when-let [s (error-message warning-type extra)] 366 | #?(:clj (binding [*out* *err*] 367 | (println (message env (str "WARNING: " s)))) 368 | :cljs (binding [*print-fn* *print-err-fn*] 369 | (println (message env (str "WARNING: " s)))))))) 370 | 371 | (def ^:dynamic *cljs-warning-handlers* 372 | [default-warning-handler]) 373 | 374 | #?(:clj 375 | (defmacro with-warning-handlers [handlers & body] 376 | `(binding [*cljs-warning-handlers* ~handlers] 377 | ~@body))) 378 | 379 | (defn- repeat-char [c n] 380 | (loop [ret c n n] 381 | (if (pos? n) 382 | (recur (str ret c) (dec n)) 383 | ret))) 384 | 385 | (defn- hex-format [s pad] 386 | #?(:clj (str "_u" (format (str "%0" pad "x") (int (first s))) "_") 387 | :cljs (let [hex (.toString (.charCodeAt s 0) 16) 388 | len (. hex -length) 389 | hex (if (< len pad) 390 | (str (repeat-char "0" (- pad len)) hex) 391 | hex)] 392 | (str "_u" hex "_")))) 393 | 394 | (defn gen-constant-id [value] 395 | (let [prefix (cond 396 | (keyword? value) "cst$kw$" 397 | (symbol? value) "cst$sym$" 398 | :else 399 | (throw 400 | #?(:clj (Exception. (str "constant type " (type value) " not supported")) 401 | :cljs (js/Error. (str "constant type " (type value) " not supported"))))) 402 | name (if (keyword? value) 403 | (subs (str value) 1) 404 | (str value)) 405 | name (if (= "." name) 406 | "_DOT_" 407 | (-> name 408 | (string/replace "-" "_DASH_") 409 | (munge) 410 | (string/replace "." "$") 411 | (string/replace #"(?i)[^a-z0-9$_]" #(hex-format % 4))))] 412 | (symbol (str prefix name)))) 413 | 414 | (defn- register-constant! 415 | ([val] (register-constant! nil val)) 416 | ([env val] 417 | (swap! env/*compiler* 418 | (fn [cenv] 419 | (cond-> 420 | (-> cenv 421 | (update-in [::constant-table] 422 | (fn [table] 423 | (if (get table val) 424 | table 425 | (assoc table val (gen-constant-id val)))))) 426 | env (update-in [::namespaces (-> env :ns :name) ::constants] 427 | (fn [{:keys [seen order] :or {seen #{} order []} :as constants}] 428 | (cond-> constants 429 | (not (contains? seen val)) 430 | (assoc 431 | :seen (conj seen val) 432 | :order (conj order val)))))))))) 433 | 434 | (def default-namespaces '{cljs.core {:name cljs.core} 435 | cljs.user {:name cljs.user}}) 436 | 437 | ;; this exists solely to support read-only namespace access from macros. 438 | ;; External tools should look at the authoritative ::namespaces slot in the 439 | ;; compiler-env atoms/maps they're using already; this value will yield only 440 | ;; `default-namespaces` when accessed outside the scope of a 441 | ;; compilation/analysis call 442 | (def namespaces 443 | #?(:clj 444 | (reify clojure.lang.IDeref 445 | (deref [_] 446 | (if-not (nil? env/*compiler*) 447 | (::namespaces @env/*compiler*) 448 | default-namespaces))) 449 | :cljs 450 | (reify IDeref 451 | (-deref [_] 452 | (if-not (nil? env/*compiler*) 453 | (::namespaces @env/*compiler*) 454 | default-namespaces))))) 455 | 456 | (defn get-namespace 457 | ([key] 458 | (get-namespace env/*compiler* key)) 459 | ([cenv key] 460 | (let [ns (get-in @cenv [::namespaces key])] 461 | (if-not (nil? ns) 462 | ns 463 | (when (= 'cljs.user key) 464 | {:name 'cljs.user}))))) 465 | 466 | #?(:clj 467 | (defmacro no-warn [& body] 468 | (let [no-warnings (zipmap (keys *cljs-warnings*) (repeat false))] 469 | `(binding [*cljs-warnings* ~no-warnings] 470 | ~@body)))) 471 | 472 | #?(:clj 473 | (defmacro all-warn [& body] 474 | (let [all-warnings (zipmap (keys *cljs-warnings*) (repeat true))] 475 | `(binding [*cljs-warnings* ~all-warnings] 476 | ~@body)))) 477 | 478 | (defn get-line [x env] 479 | (or (-> x meta :line) (:line env))) 480 | 481 | (defn get-col [x env] 482 | (or (-> x meta :column) (:column env))) 483 | 484 | (defn intern-macros 485 | "Given a Clojure namespace intern all macros into the ambient ClojureScript 486 | analysis environment." 487 | ([ns] (intern-macros ns false)) 488 | ([ns reload] 489 | (when (or (nil? (get-in @env/*compiler* [::namespaces ns :macros])) 490 | reload) 491 | (swap! env/*compiler* assoc-in [::namespaces ns :macros] 492 | (->> #?(:clj (ns-interns ns) :cljs (ns-interns* ns)) 493 | (filter (fn [[_ ^Var v]] (.isMacro v))) 494 | (map (fn [[k v]] 495 | [k (as-> (meta v) vm 496 | (let [ns (.getName ^Namespace (:ns vm))] 497 | (assoc vm 498 | :ns ns 499 | :name (symbol (str ns) (str k)))))])) 500 | (into {})))))) 501 | 502 | #?(:clj 503 | (defn load-core [] 504 | (when (not @-cljs-macros-loaded) 505 | (reset! -cljs-macros-loaded true) 506 | (if *cljs-macros-is-classpath* 507 | (load *cljs-macros-path*) 508 | (load-file *cljs-macros-path*))) 509 | (intern-macros 'cljs.core))) 510 | 511 | #?(:clj 512 | (defmacro with-core-macros 513 | [path & body] 514 | `(do 515 | (when (not= *cljs-macros-path* ~path) 516 | (reset! -cljs-macros-loaded false)) 517 | (binding [*cljs-macros-path* ~path] 518 | ~@body)))) 519 | 520 | #?(:clj 521 | (defmacro with-core-macros-file 522 | [path & body] 523 | `(do 524 | (when (not= *cljs-macros-path* ~path) 525 | (reset! -cljs-macros-loaded false)) 526 | (binding [*cljs-macros-path* ~path 527 | *cljs-macros-is-classpath* false] 528 | ~@body)))) 529 | 530 | (defn empty-env 531 | "Construct an empty analysis environment. Required to analyze forms." 532 | [] 533 | (ensure 534 | {:ns (get-namespace *cljs-ns*) 535 | :context :statement 536 | :locals {} 537 | :fn-scope [] 538 | :js-globals (into {} 539 | (map #(vector % {:name %}) 540 | '(alert window document console escape unescape 541 | screen location navigator history location 542 | global process require module exports)))})) 543 | 544 | (defn source-info 545 | ([env] 546 | (when-let [line (:line env)] 547 | {:file (if (= (-> env :ns :name) 'cljs.core) 548 | "cljs/core.cljs" 549 | *cljs-file*) 550 | :line (get-line name env) 551 | :column (get-col name env)})) 552 | ([name env] 553 | {:file (if (= (-> env :ns :name) 'cljs.core) 554 | "cljs/core.cljs" 555 | *cljs-file*) 556 | :line (get-line name env) 557 | :column (get-col name env)})) 558 | 559 | (defn message [env s] 560 | (str s (when (:line env) 561 | (str " at line " (:line env) " " *cljs-file*)))) 562 | 563 | (defn warning [warning-type env extra] 564 | (doseq [handler *cljs-warning-handlers*] 565 | (handler warning-type env extra))) 566 | 567 | (defn error 568 | ([env msg] 569 | (error env msg nil)) 570 | ([env msg cause] 571 | (ex-info (message env msg) 572 | (assoc (source-info env) :tag :cljs/analysis-error) 573 | cause))) 574 | 575 | (defn analysis-error? 576 | #?(:cljs {:tag boolean}) 577 | [ex] 578 | (= :cljs/analysis-error (:tag (ex-data ex)))) 579 | 580 | #?(:clj 581 | (defmacro wrapping-errors [env & body] 582 | `(try 583 | ~@body 584 | (catch Throwable err# 585 | (if (analysis-error? err#) 586 | (throw err#) 587 | (throw (error ~env (.getMessage err#) err#))))))) 588 | 589 | ;; namespaces implicit to the inclusion of cljs.core 590 | (def implicit-nses '#{goog goog.object goog.string goog.array Math}) 591 | 592 | (defn implicit-import? 593 | #?(:cljs {:tag boolean}) 594 | [env prefix suffix] 595 | (contains? implicit-nses prefix)) 596 | 597 | (defn confirm-var-exist-warning [env prefix suffix] 598 | (fn [env prefix suffix] 599 | (warning :undeclared-var env {:prefix prefix :suffix suffix}))) 600 | 601 | (defn loaded-js-ns? 602 | "Check if a JavaScript namespace has been loaded. JavaScript vars are 603 | not currently checked." 604 | #?(:cljs {:tag boolean}) 605 | [env prefix] 606 | (when-not (gets @env/*compiler* ::namespaces prefix) 607 | (let [ns (:ns env)] 608 | (if-not (nil? (get (:requires ns) prefix)) 609 | true 610 | (if-not (nil? (get (:imports ns) prefix)) 611 | true 612 | false))))) 613 | 614 | (defn confirm-var-exists 615 | ([env prefix suffix] 616 | (let [warn (confirm-var-exist-warning env prefix suffix)] 617 | (confirm-var-exists env prefix suffix warn))) 618 | ([env prefix suffix missing-fn] 619 | (let [sufstr (str suffix) 620 | suffix-str (if (and #?(:clj (not= ".." sufstr) 621 | :cljs (not (identical? ".." sufstr))) ;; leave cljs.core$macros/.. alone 622 | #?(:clj (re-find #"\." sufstr) 623 | :cljs ^boolean (.test #"\." sufstr))) 624 | (first (string/split sufstr #"\.")) 625 | suffix) 626 | suffix (symbol suffix-str)] 627 | (when (and (not (implicit-import? env prefix suffix)) 628 | (not (loaded-js-ns? env prefix)) 629 | (not (and (= 'cljs.core prefix) (= 'unquote suffix))) 630 | (nil? (gets @env/*compiler* ::namespaces prefix :defs suffix))) 631 | (missing-fn env prefix suffix))))) 632 | 633 | (defn confirm-var-exists-throw [] 634 | (fn [env prefix suffix] 635 | (confirm-var-exists env prefix suffix 636 | (fn [env prefix suffix] 637 | (throw (error env (str "Unable to resolve var: " suffix " in this context"))))))) 638 | 639 | (defn resolve-ns-alias [env name] 640 | (let [sym (symbol name)] 641 | (get (:requires (:ns env)) sym sym))) 642 | 643 | (defn resolve-macro-ns-alias [env name] 644 | (let [sym (symbol name)] 645 | (get (:require-macros (:ns env)) sym sym))) 646 | 647 | (defn confirm-ns 648 | "Given env, an analysis environment, and ns-sym, a symbol identifying a 649 | namespace, confirm that the namespace exists. Warn if not found." 650 | [env ns-sym] 651 | (when (and (not= 'cljs.core ns-sym) 652 | (nil? (get implicit-nses ns-sym)) 653 | (nil? (get (-> env :ns :requires) ns-sym)) 654 | ;; something else may have loaded the namespace, i.e. load-file 655 | (nil? (gets @env/*compiler* ::namespaces ns-sym)) 656 | ;; macros may refer to namespaces never explicitly required 657 | ;; confirm that the library at least exists 658 | #?(:clj (nil? (util/ns->source ns-sym)))) 659 | (warning :undeclared-ns env {:ns-sym ns-sym}))) 660 | 661 | (declare get-expander) 662 | 663 | (defn core-name? 664 | "Is sym visible from core in the current compilation namespace?" 665 | #?(:cljs {:tag boolean}) 666 | [env sym] 667 | (and (or (gets @env/*compiler* ::namespaces 'cljs.core :defs sym) 668 | (when-let [mac (get-expander sym env)] 669 | (let [^Namespace ns (-> mac meta :ns)] 670 | (= (.getName ns) #?(:clj 'cljs.core :cljs 'cljs.core$macros))))) 671 | (not (contains? (-> env :ns :excludes) sym)))) 672 | 673 | (defn resolve-var 674 | "Resolve a var. Accepts a side-effecting confirm fn for producing 675 | warnings about unresolved vars." 676 | ([env sym] (resolve-var env sym nil)) 677 | ([env sym confirm] 678 | (if #?(:clj (= "js" (namespace sym)) 679 | :cljs (identical? "js" (namespace sym))) 680 | {:name sym :ns 'js} 681 | (let [s (str sym) 682 | lcls (:locals env) 683 | lb (get lcls sym)] 684 | (cond 685 | (not (nil? lb)) lb 686 | 687 | (not (nil? (namespace sym))) 688 | (let [ns (namespace sym) 689 | ns (if #?(:clj (= "clojure.core" ns) 690 | :cljs (identical? "clojure.core" ns)) 691 | "cljs.core" 692 | ns) 693 | full-ns (resolve-ns-alias env ns)] 694 | (when-not (nil? confirm) 695 | (when (not= (-> env :ns :name) full-ns) 696 | (confirm-ns env full-ns)) 697 | (confirm env full-ns (symbol (name sym)))) 698 | (merge (gets @env/*compiler* ::namespaces full-ns :defs (symbol (name sym))) 699 | {:name (symbol (str full-ns) (str (name sym))) 700 | :ns full-ns})) 701 | 702 | #?(:clj (and (.contains s ".") 703 | (not (.contains s ".."))) 704 | :cljs (and ^boolean (goog.string/contains s ".") 705 | (not ^boolean (goog.string/contains s "..")))) 706 | (let [idx (.indexOf s ".") 707 | prefix (symbol (subs s 0 idx)) 708 | suffix (subs s (inc idx)) 709 | lb (get lcls prefix)] 710 | (if-not (nil? lb) 711 | {:name (symbol (str (:name lb)) suffix)} 712 | (let [cur-ns (-> env :ns :name) 713 | full-ns (gets @env/*compiler* ::namespaces cur-ns :imports prefix)] 714 | (if-not (nil? full-ns) 715 | {:name (symbol (str full-ns) suffix)} 716 | (let [info (gets @env/*compiler* ::namespaces cur-ns :defs prefix)] 717 | (if-not (nil? info) 718 | (merge info 719 | {:name (symbol (str cur-ns) (str sym)) 720 | :ns cur-ns}) 721 | (merge (gets @env/*compiler* ::namespaces prefix :defs (symbol suffix)) 722 | {:name (if (= "" prefix) (symbol suffix) (symbol (str prefix) suffix)) 723 | :ns prefix}))))))) 724 | 725 | (not (nil? (gets @env/*compiler* ::namespaces (-> env :ns :name) :uses sym))) 726 | (let [full-ns (gets @env/*compiler* ::namespaces (-> env :ns :name) :uses sym)] 727 | (merge 728 | (gets @env/*compiler* ::namespaces full-ns :defs sym) 729 | {:name (symbol (str full-ns) (str sym)) 730 | :ns (-> env :ns :name)})) 731 | 732 | (not (nil? (gets @env/*compiler* ::namespaces (-> env :ns :name) :imports sym))) 733 | (recur env (gets @env/*compiler* ::namespaces (-> env :ns :name) :imports sym) confirm) 734 | 735 | :else 736 | (let [cur-ns (-> env :ns :name) 737 | full-ns (cond 738 | (not (nil? (gets @env/*compiler* ::namespaces cur-ns :defs sym))) cur-ns 739 | (core-name? env sym) 'cljs.core 740 | :else cur-ns)] 741 | (when-not (nil? confirm) 742 | (confirm env full-ns sym)) 743 | (merge (gets @env/*compiler* ::namespaces full-ns :defs sym) 744 | {:name (symbol (str full-ns) (str sym)) 745 | :ns full-ns}))))))) 746 | 747 | (defn resolve-existing-var 748 | "Given env, an analysis environment, and sym, a symbol, resolve an existing var. 749 | Emits a warning if no such var exists." 750 | [env sym] 751 | (if-not (-> sym meta ::no-resolve) 752 | (resolve-var env sym confirm-var-exists) 753 | (resolve-var env sym))) 754 | 755 | (defn confirm-bindings 756 | "Given env, an analysis environment env, and names, a list of symbols, confirm 757 | that all correspond to declared dynamic vars." 758 | [env names] 759 | (doseq [name names] 760 | (let [env (assoc env :ns (get-namespace *cljs-ns*)) 761 | ev (resolve-existing-var env name)] 762 | (when (and ev (not (-> ev :dynamic))) 763 | (warning :dynamic env {:ev ev :name (:name ev)}))))) 764 | 765 | (defn resolve-macro-var 766 | "Given env, an analysis environment, and sym, a symbol, resolve a macro." 767 | [env sym] 768 | (let [ns (-> env :ns :name) 769 | namespaces (get @env/*compiler* ::namespaces)] 770 | (cond 771 | (namespace sym) 772 | (let [ns (namespace sym) 773 | ns (if (= "clojure.core" ns) "cljs.core" ns) 774 | full-ns (resolve-macro-ns-alias env ns)] 775 | (get-in namespaces [full-ns :macros (symbol (name sym))])) 776 | 777 | (get-in namespaces [ns :use-macros sym]) 778 | (let [full-ns (get-in namespaces [ns :use-macros sym])] 779 | (get-in namespaces [full-ns :macros sym])) 780 | 781 | :else 782 | (let [ns (cond 783 | (get-in namespaces [ns :macros sym]) ns 784 | (core-name? env sym) 'cljs.core)] 785 | (when ns 786 | (get-in namespaces [ns :macros sym])))))) 787 | 788 | (declare analyze analyze-symbol analyze-seq) 789 | 790 | (def specials '#{if def fn* do let* loop* letfn* throw try recur new set! 791 | ns deftype* defrecord* . js* & quote case* var}) 792 | 793 | (def ^:dynamic *recur-frames* nil) 794 | (def ^:dynamic *loop-lets* ()) 795 | (def ^:dynamic *allow-redef* false) 796 | 797 | #?(:clj 798 | (defmacro disallowing-recur [& body] 799 | `(binding [*recur-frames* (cons nil *recur-frames*)] ~@body))) 800 | 801 | #?(:clj 802 | (defmacro allowing-redef [& body] 803 | `(binding [*allow-redef* true] ~@body))) 804 | 805 | ;; TODO: move this logic out - David 806 | (defn analyze-keyword 807 | [env sym] 808 | (register-constant! env sym) 809 | {:op :constant :env env :form sym :tag 'cljs.core/Keyword}) 810 | 811 | (defn get-tag [e] 812 | (let [tag (-> e :tag)] 813 | (if-not (nil? tag) 814 | tag 815 | (let [tag (-> e :info :tag)] 816 | (if-not (nil? tag) 817 | tag 818 | (-> e :form meta :tag)))))) 819 | 820 | (defn find-matching-method [f params] 821 | ;; if local fn, need to look in :info 822 | (let [methods (or (:methods f) (-> f :info :methods)) 823 | c (count params)] 824 | (some 825 | (fn [m] 826 | (and (or (== (:max-fixed-arity m) c) 827 | (:variadic m)) 828 | m)) 829 | methods))) 830 | 831 | (defn type? 832 | #?(:cljs {:tag boolean}) 833 | [env t] 834 | ;; don't use resolve-existing-var to avoid warnings 835 | (when (and (not (nil? t)) (symbol? t)) 836 | (let [var (resolve-var env t) 837 | type (:type var)] 838 | (if-not (nil? type) 839 | type 840 | (let [type (-> var :info :type)] 841 | (if-not (nil? type) 842 | type 843 | (let [proto (:protocol-symbol var)] 844 | (if-not (nil? proto) 845 | proto 846 | (get '#{cljs.core/PersistentHashMap cljs.core/List} t))))))))) 847 | 848 | (declare infer-tag) 849 | 850 | (def NOT_NATIVE '#{clj not-native}) 851 | 852 | (def BOOLEAN_OR_SEQ '#{boolean seq}) 853 | 854 | (defn infer-if [env e] 855 | (let [{{:keys [op form]} :test} e 856 | then-tag (infer-tag env (:then e))] 857 | (if (and #?(:clj (= op :constant) 858 | :cljs (keyword-identical? op :constant)) 859 | (not (nil? form)) 860 | (not (false? form))) 861 | then-tag 862 | (let [else-tag (infer-tag env (:else e))] 863 | (cond 864 | (or #?(:clj (= then-tag else-tag) 865 | :cljs (symbol-identical? then-tag else-tag)) 866 | #?(:clj (= else-tag IGNORE_SYM) 867 | :cljs (symbol-identical? else-tag IGNORE_SYM))) then-tag 868 | #?(:clj (= then-tag IGNORE_SYM) 869 | :cljs (symbol-identical? then-tag IGNORE_SYM)) else-tag 870 | ;; TODO: temporary until we move not-native -> clj - David 871 | (and (or (not (nil? (get NOT_NATIVE then-tag))) (type? env then-tag)) 872 | (or (not (nil? (get NOT_NATIVE else-tag))) (type? env else-tag))) 873 | 'clj 874 | :else 875 | (if (and (not (nil? (get BOOLEAN_OR_SEQ then-tag))) 876 | (not (nil? (get BOOLEAN_OR_SEQ else-tag)))) 877 | 'seq 878 | (let [then-tag (if #?(:clj (set? then-tag) 879 | :cljs (cljs-set? then-tag)) 880 | then-tag #{then-tag}) 881 | else-tag (if #?(:clj (set? else-tag) 882 | :cljs (cljs-set? else-tag)) 883 | else-tag #{else-tag})] 884 | (into then-tag else-tag)))))))) 885 | 886 | (defn infer-invoke [env e] 887 | (let [{info :info :as f} (:f e) 888 | ret-tag (when-not (nil? (:fn-var info)) (:ret-tag info))] 889 | (if-not (nil? ret-tag) 890 | ret-tag 891 | (let [args (:args e) 892 | me (assoc (find-matching-method f args) :op :method) 893 | ret-tag (infer-tag env me)] 894 | (if-not (nil? ret-tag) 895 | ret-tag 896 | ANY_SYM))))) 897 | 898 | (defn infer-tag 899 | "Given env, an analysis environment, and e, an AST node, return the inferred 900 | type of the node" 901 | [env e] 902 | (let [tag (get-tag e)] 903 | (if-not (nil? tag) 904 | tag 905 | (case (:op e) 906 | :recur IGNORE_SYM 907 | :throw IGNORE_SYM 908 | :let (infer-tag env (:expr e)) 909 | :loop (infer-tag env (:expr e)) 910 | :do (infer-tag env (:ret e)) 911 | :method (infer-tag env (:expr e)) 912 | :def (infer-tag env (:init e)) 913 | :invoke (infer-invoke env e) 914 | :if (infer-if env e) 915 | :constant (case (:form e) 916 | true BOOLEAN_SYM 917 | false BOOLEAN_SYM 918 | ANY_SYM) 919 | :var (if-not (nil? (:init e)) 920 | (infer-tag env (:init e)) 921 | (infer-tag env (:info e))) 922 | :dot ANY_SYM 923 | :js ANY_SYM 924 | nil)))) 925 | 926 | (defmulti parse (fn [op & rest] op)) 927 | 928 | (defn- var-ast 929 | [env sym] 930 | (let [var (resolve-var env sym (confirm-var-exists-throw)) 931 | expr-env (assoc env :context :expr)] 932 | (if-let [var-ns (:ns var)] 933 | {:var (analyze expr-env sym) 934 | :sym (analyze expr-env `(quote ~(symbol (name var-ns) (name (:name var))))) 935 | :meta (let [ks [:ns :doc :file :line :column] 936 | m (merge 937 | (let [user-meta (:meta var) 938 | uks (keys user-meta)] 939 | (zipmap uks 940 | (map #(list 'quote (get user-meta %)) uks))) 941 | (assoc (zipmap ks (map #(list 'quote (get var %)) ks)) 942 | :name `(quote ~(symbol (name (:name var)))) 943 | :test `(when ~sym (.-cljs$lang$test ~sym)) 944 | :arglists (let [arglists (:arglists var) 945 | arglists' (if (= 'quote (first arglists)) 946 | (second arglists) 947 | arglists)] 948 | (list 'quote 949 | (doall (map with-meta arglists' 950 | (:arglists-meta var)))))))] 951 | (analyze expr-env m))}))) 952 | 953 | (defmethod parse 'var 954 | [op env [_ sym :as form] _ _] 955 | (merge 956 | {:env env 957 | :op :var-special 958 | :form form} 959 | (var-ast env sym))) 960 | 961 | (defmethod parse 'if 962 | [op env [_ test then else :as form] name _] 963 | (when (< (count form) 3) 964 | (throw (error env "Too few arguments to if"))) 965 | (when (> (count form) 4) 966 | (throw (error env "Too many arguments to if"))) 967 | (let [test-expr (disallowing-recur (analyze (assoc env :context :expr) test)) 968 | then-expr (allowing-redef (analyze env then)) 969 | else-expr (allowing-redef (analyze env else))] 970 | {:env env :op :if :form form 971 | :test test-expr :then then-expr :else else-expr 972 | :unchecked #?(:clj @*unchecked-if* 973 | :cljs *unchecked-if*) 974 | :children [test-expr then-expr else-expr]})) 975 | 976 | (defmethod parse 'case* 977 | [op env [_ sym tests thens default :as form] name _] 978 | (assert (symbol? sym) "case* must switch on symbol") 979 | (assert (every? vector? tests) "case* tests must be grouped in vectors") 980 | (let [expr-env (assoc env :context :expr) 981 | v (disallowing-recur (analyze expr-env sym)) 982 | tests (mapv #(mapv (fn [t] (analyze expr-env t)) %) tests) 983 | thens (mapv #(analyze env %) thens) 984 | default (analyze env default)] 985 | (assert (every? (fn [t] 986 | (or 987 | (-> t :info :const) 988 | (and (= :constant (:op t)) 989 | ((some-fn number? string? char?) (:form t))))) 990 | (apply concat tests)) 991 | "case* tests must be numbers, strings, or constants") 992 | {:env env :op :case* :form form 993 | :v v :tests tests :thens thens :default default 994 | :children (vec (concat [v] tests thens (if default [default])))})) 995 | 996 | (defmethod parse 'throw 997 | [op env [_ throw :as form] name _] 998 | (let [throw-expr (disallowing-recur (analyze (assoc env :context :expr) throw))] 999 | {:env env :op :throw :form form 1000 | :throw throw-expr 1001 | :children [throw-expr]})) 1002 | 1003 | (defmethod parse 'try 1004 | [op env [_ & body :as form] name _] 1005 | (let [catchenv (update-in env [:context] #(if (= :expr %) :return %)) 1006 | catch? (every-pred seq? #(= (first %) 'catch)) 1007 | default? (every-pred catch? #(= (second %) :default)) 1008 | finally? (every-pred seq? #(= (first %) 'finally)) 1009 | 1010 | {:keys [body cblocks dblock fblock]} 1011 | (loop [parser {:state :start :forms body 1012 | :body [] :cblocks [] :dblock nil :fblock nil}] 1013 | (if (seq? (:forms parser)) 1014 | (let [[form & forms*] (:forms parser) 1015 | parser* (assoc parser :forms forms*)] 1016 | (case (:state parser) 1017 | :start (cond 1018 | (catch? form) (recur (assoc parser :state :catches)) 1019 | (finally? form) (recur (assoc parser :state :finally)) 1020 | :else (recur (update-in parser* [:body] conj form))) 1021 | :catches (cond 1022 | (default? form) (recur (assoc parser* :dblock form :state :finally)) 1023 | (catch? form) (recur (update-in parser* [:cblocks] conj form)) 1024 | (finally? form) (recur (assoc parser :state :finally)) 1025 | :else (throw (error env "Invalid try form"))) 1026 | :finally (recur (assoc parser* :fblock form :state :done)) 1027 | :done (throw (error env "Unexpected form after finally")))) 1028 | parser)) 1029 | 1030 | finally (when (seq fblock) 1031 | (analyze (assoc env :context :statement) `(do ~@(rest fblock)))) 1032 | e (when (or (seq cblocks) dblock) (gensym "e")) 1033 | default (if-let [[_ _ name & cb] dblock] 1034 | `(cljs.core/let [~name ~e] ~@cb) 1035 | `(throw ~e)) 1036 | cblock (if (seq cblocks) 1037 | `(cljs.core/cond 1038 | ~@(mapcat 1039 | (fn [[_ type name & cb]] 1040 | (when name (assert (not (namespace name)) "Can't qualify symbol in catch")) 1041 | `[(cljs.core/instance? ~type ~e) 1042 | (cljs.core/let [~name ~e] ~@cb)]) 1043 | cblocks) 1044 | :else ~default) 1045 | default) 1046 | locals (:locals catchenv) 1047 | locals (if e 1048 | (assoc locals e 1049 | {:name e 1050 | :line (get-line e env) 1051 | :column (get-col e env)}) 1052 | locals) 1053 | catch (when cblock 1054 | (analyze (assoc catchenv :locals locals) cblock)) 1055 | try (analyze (if (or e finally) catchenv env) `(do ~@body))] 1056 | 1057 | {:env env :op :try :form form 1058 | :try try 1059 | :finally finally 1060 | :name e 1061 | :catch catch 1062 | :children [try catch finally]})) 1063 | 1064 | (defmethod parse 'def 1065 | [op env form name _] 1066 | (let [pfn (fn 1067 | ([_ sym] {:sym sym}) 1068 | ([_ sym init] {:sym sym :init init}) 1069 | ([_ sym doc init] {:sym sym :doc doc :init init})) 1070 | args (apply pfn form) 1071 | sym (:sym args) 1072 | sym-meta (meta sym) 1073 | tag (-> sym meta :tag) 1074 | protocol (-> sym meta :protocol) 1075 | dynamic (-> sym meta :dynamic) 1076 | ns-name (-> env :ns :name) 1077 | locals (:locals env) 1078 | clash-ns (symbol (str ns-name "." sym))] 1079 | (when (get-in @env/*compiler* [::namespaces clash-ns]) 1080 | (warning :ns-var-clash env 1081 | {:ns (symbol (str ns-name "." sym)) 1082 | :var (symbol (str ns-name) (str sym))})) 1083 | (when (namespace sym) 1084 | (throw (error env "Can't def ns-qualified name"))) 1085 | (when (:const (resolve-var (dissoc env :locals) sym)) 1086 | (throw (error env "Can't redefine a constant"))) 1087 | (when-let [doc (:doc args)] 1088 | (when-not (string? doc) 1089 | (throw (error env "Too many arguments to def")))) 1090 | (when-let [v (get-in @env/*compiler* [::namespaces ns-name :defs sym])] 1091 | (when (and (not *allow-redef*) 1092 | (not (:declared v)) 1093 | (not (:declared sym-meta)) 1094 | *file-defs* 1095 | (get @*file-defs* sym)) 1096 | (warning :redef-in-file env {:sym sym :line (:line v)}))) 1097 | (when *file-defs* 1098 | (swap! *file-defs* conj sym)) 1099 | (let [env (if (or (and (not= ns-name 'cljs.core) 1100 | (core-name? env sym)) 1101 | (get-in @env/*compiler* [::namespaces ns-name :uses sym])) 1102 | (let [ev (resolve-existing-var (dissoc env :locals) sym)] 1103 | (warning :redef env {:sym sym :ns (:ns ev) :ns-name ns-name}) 1104 | (swap! env/*compiler* update-in [::namespaces ns-name :excludes] conj sym) 1105 | (update-in env [:ns :excludes] conj sym)) 1106 | env) 1107 | var-name (:name (resolve-var (dissoc env :locals) sym)) 1108 | init-expr (when (contains? args :init) 1109 | (swap! env/*compiler* assoc-in [::namespaces ns-name :defs sym] 1110 | (merge 1111 | {:name var-name} 1112 | sym-meta 1113 | (when dynamic {:dynamic true}) 1114 | (source-info var-name env))) 1115 | (disallowing-recur 1116 | (analyze (assoc env :context :expr) (:init args) sym))) 1117 | fn-var? (and init-expr (= (:op init-expr) :fn)) 1118 | tag (if fn-var? 1119 | (or (:ret-tag init-expr) tag) 1120 | tag) 1121 | export-as (when-let [export-val (-> sym meta :export)] 1122 | (if (= true export-val) var-name export-val)) 1123 | doc (or (:doc args) (-> sym meta :doc))] 1124 | (when-let [v (get-in @env/*compiler* [::namespaces ns-name :defs sym])] 1125 | (when (and (not (-> sym meta :declared)) 1126 | (and (:fn-var v) (not fn-var?))) 1127 | (warning :fn-var env {:ns-name ns-name :sym sym}))) 1128 | (swap! env/*compiler* assoc-in [::namespaces ns-name :defs sym] 1129 | (merge 1130 | {:name var-name} 1131 | ;; remove actual test metadata, as it includes non-valid EDN and 1132 | ;; cannot be present in analysis cached to disk - David 1133 | (cond-> sym-meta 1134 | (:test sym-meta) (assoc :test true)) 1135 | {:meta (-> sym-meta 1136 | (dissoc :test) 1137 | (update-in [:file] 1138 | (fn [f] 1139 | (if (= (-> env :ns :name) 'cljs.core) 1140 | "cljs/core.cljs" 1141 | f))))} 1142 | (when doc {:doc doc}) 1143 | (when dynamic {:dynamic true}) 1144 | (source-info var-name env) 1145 | ;; the protocol a protocol fn belongs to 1146 | (when protocol 1147 | {:protocol protocol}) 1148 | ;; symbol for reified protocol 1149 | (when-let [protocol-symbol (-> sym meta :protocol-symbol)] 1150 | {:protocol-symbol protocol-symbol 1151 | :info (-> protocol-symbol meta :protocol-info) 1152 | :impls #{}}) 1153 | (when fn-var? 1154 | (let [params (map #(vec (map :name (:params %))) (:methods init-expr))] 1155 | (merge 1156 | {:fn-var true 1157 | ;; protocol implementation context 1158 | :protocol-impl (:protocol-impl init-expr) 1159 | ;; inline protocol implementation context 1160 | :protocol-inline (:protocol-inline init-expr)} 1161 | (if-let [top-fn-meta (:top-fn sym-meta)] 1162 | top-fn-meta 1163 | {:variadic (:variadic init-expr) 1164 | :max-fixed-arity (:max-fixed-arity init-expr) 1165 | :method-params params 1166 | :arglists (:arglists sym-meta) 1167 | :arglists-meta (doall (map meta (:arglists sym-meta)))}))) ) 1168 | (when (and fn-var? tag) 1169 | {:ret-tag tag}))) 1170 | (merge 1171 | {:env env 1172 | :op :def 1173 | :form form 1174 | :name var-name 1175 | :var (assoc 1176 | (analyze 1177 | (-> env (dissoc :locals) 1178 | (assoc :context :expr) 1179 | (assoc :def-var true)) 1180 | sym) 1181 | :op :var) 1182 | :doc doc 1183 | :jsdoc (:jsdoc sym-meta) 1184 | :init init-expr} 1185 | (when (:def-emits-var env) 1186 | {:var-ast (var-ast env sym)}) 1187 | (when-let [test (:test sym-meta)] 1188 | {:test (analyze (assoc env :context :expr) test)}) 1189 | (when tag 1190 | (if fn-var? 1191 | {:ret-tag tag} 1192 | {:tag tag})) 1193 | (when dynamic {:dynamic true}) 1194 | (when export-as {:export export-as}) 1195 | (when init-expr {:children [init-expr]}))))) 1196 | 1197 | (defn analyze-fn-method-param [env] 1198 | (fn [[locals params] name] 1199 | (let [line (get-line name env) 1200 | column (get-col name env) 1201 | nmeta (meta name) 1202 | tag (:tag nmeta) 1203 | shadow (when-not (nil? locals) 1204 | (locals name)) 1205 | env (merge (select-keys env [:context]) 1206 | {:line line :column column}) 1207 | param {:op :var 1208 | :name name 1209 | :line line 1210 | :column column 1211 | :tag tag 1212 | :shadow shadow 1213 | ;; Give the fn params the same shape 1214 | ;; as a :var, so it gets routed 1215 | ;; correctly in the compiler 1216 | :env env 1217 | :info {:name name :shadow shadow} 1218 | :binding-form? true}] 1219 | [(assoc locals name param) (conj params param)]))) 1220 | 1221 | (defn analyze-fn-method-body [env form recur-frames] 1222 | (binding [*recur-frames* recur-frames] 1223 | (analyze env form))) 1224 | 1225 | (defn- analyze-fn-method [env locals form type] 1226 | (let [param-names (first form) 1227 | variadic (boolean (some '#{&} param-names)) 1228 | param-names (vec (remove '#{&} param-names)) 1229 | body (next form) 1230 | step (analyze-fn-method-param env) 1231 | step-init [locals []] 1232 | [locals params] (reduce step step-init param-names) 1233 | params' (if (true? variadic) 1234 | (butlast params) 1235 | params) 1236 | fixed-arity (count params') 1237 | recur-frame {:params params :flag (atom nil)} 1238 | recur-frames (cons recur-frame *recur-frames*) 1239 | body-env (assoc env :context :return :locals locals) 1240 | body-form `(do ~@body) 1241 | expr (analyze-fn-method-body body-env body-form recur-frames) 1242 | recurs @(:flag recur-frame)] 1243 | {:env env 1244 | :variadic variadic 1245 | :params params 1246 | :max-fixed-arity fixed-arity 1247 | :type type 1248 | :form form 1249 | :expr expr 1250 | :recurs recurs})) 1251 | 1252 | (declare analyze-wrap-meta) 1253 | 1254 | (defn fn-name-var [env locals name] 1255 | (when-not (nil? name) 1256 | (let [ns (-> env :ns :name) 1257 | shadow (get locals name) 1258 | shadow (when (nil? shadow) 1259 | (get-in env [:js-globals name])) 1260 | fn-scope (:fn-scope env) 1261 | name-var {:name name 1262 | :info {:fn-self-name true 1263 | :fn-scope fn-scope 1264 | :ns ns 1265 | :shadow shadow}} 1266 | tag (-> name meta :tag) 1267 | ret-tag (when-not (nil? tag) 1268 | {:ret-tag tag})] 1269 | (merge name-var ret-tag)))) 1270 | 1271 | (defn analyze-fn-methods-pass2* [menv locals type meths] 1272 | (doall (map #(analyze-fn-method menv locals % type) meths))) 1273 | 1274 | (defn analyze-fn-methods-pass2 [menv locals type meths] 1275 | (no-warn (analyze-fn-methods-pass2* menv locals type meths))) 1276 | 1277 | (defmethod parse 'fn* 1278 | [op env [_ & args :as form] name _] 1279 | (let [[name meths] (if (symbol? (first args)) 1280 | [(first args) (next args)] 1281 | [name (seq args)]) 1282 | ;; turn (fn [] ...) into (fn ([]...)) 1283 | meths (if (vector? (first meths)) 1284 | (list meths) 1285 | meths) 1286 | locals (:locals env) 1287 | name-var (fn-name-var env locals name) 1288 | env (if-not (nil? name) 1289 | (update-in env [:fn-scope] conj name-var) 1290 | env) 1291 | locals (if (and (not (nil? locals)) 1292 | (not (nil? name))) 1293 | (assoc locals name name-var) 1294 | locals) 1295 | form-meta (meta form) 1296 | type (::type form-meta) 1297 | proto-impl (::protocol-impl form-meta) 1298 | proto-inline (::protocol-inline form-meta) 1299 | menv (if (> (count meths) 1) 1300 | (assoc env :context :expr) 1301 | env) 1302 | menv (merge menv 1303 | {:protocol-impl proto-impl 1304 | :protocol-inline proto-inline}) 1305 | methods (map #(analyze-fn-method menv locals % type) meths) 1306 | mfa (apply max (map :max-fixed-arity methods)) 1307 | variadic (boolean (some :variadic methods)) 1308 | locals (if-not (nil? name) 1309 | (update-in locals [name] assoc 1310 | ;; TODO: can we simplify? - David 1311 | :fn-var true 1312 | :variadic variadic 1313 | :max-fixed-arity mfa 1314 | :method-params (map :params methods)) 1315 | locals) 1316 | methods (if-not (nil? name) 1317 | ;; a second pass with knowledge of our function-ness/arity 1318 | ;; lets us optimize self calls 1319 | (analyze-fn-methods-pass2 menv locals type meths) 1320 | methods) 1321 | form (vary-meta form dissoc ::protocol-impl ::protocol-inline ::type) 1322 | js-doc (when (true? variadic) 1323 | "@param {...*} var_args") 1324 | children (mapv :expr methods) 1325 | ast {:op :fn 1326 | :env env 1327 | :form form 1328 | :name name-var 1329 | :methods methods 1330 | :variadic variadic 1331 | :tag 'function 1332 | :recur-frames *recur-frames* 1333 | :loop-lets *loop-lets* 1334 | :jsdoc [js-doc] 1335 | :max-fixed-arity mfa 1336 | :protocol-impl proto-impl 1337 | :protocol-inline proto-inline 1338 | :children children}] 1339 | (let [variadic-methods (filter :variadic methods) 1340 | variadic-params (count (:params (first variadic-methods))) 1341 | param-counts (map (comp count :params) methods)] 1342 | (when (< 1 (count variadic-methods)) 1343 | (warning :multiple-variadic-overloads env {:name name-var})) 1344 | (when (not (or (zero? variadic-params) (== variadic-params (+ 1 mfa)))) 1345 | (warning :variadic-max-arity env {:name name-var})) 1346 | (when (not= (distinct param-counts) param-counts) 1347 | (warning :overload-arity env {:name name-var}))) 1348 | (analyze-wrap-meta ast))) 1349 | 1350 | (defmethod parse 'letfn* 1351 | [op env [_ bindings & exprs :as form] name _] 1352 | (when-not (and (vector? bindings) (even? (count bindings))) 1353 | (throw (error env "bindings must be vector of even number of elements"))) 1354 | (let [n->fexpr (into {} (map (juxt first second) (partition 2 bindings))) 1355 | names (keys n->fexpr) 1356 | context (:context env) 1357 | ;; first pass to collect information for recursive references 1358 | [meth-env bes] 1359 | (reduce (fn [[{:keys [locals] :as env} bes] n] 1360 | (let [ret-tag (-> n meta :tag) 1361 | fexpr (no-warn (analyze env (n->fexpr n))) 1362 | be (cond-> 1363 | {:name n 1364 | :fn-var true 1365 | :line (get-line n env) 1366 | :column (get-col n env) 1367 | :local true 1368 | :shadow (locals n) 1369 | :variadic (:variadic fexpr) 1370 | :max-fixed-arity (:max-fixed-arity fexpr) 1371 | :method-params (map :params (:methods fexpr))} 1372 | ret-tag (assoc :ret-tag ret-tag))] 1373 | [(assoc-in env [:locals n] be) 1374 | (conj bes be)])) 1375 | [env []] names) 1376 | meth-env (assoc meth-env :context :expr) 1377 | ;; the real pass 1378 | [meth-env bes] 1379 | (reduce (fn [[meth-env bes] {:keys [name shadow] :as be}] 1380 | (let [env (assoc-in meth-env [:locals name] shadow) 1381 | fexpr (analyze env (n->fexpr name)) 1382 | be' (assoc be 1383 | :init fexpr 1384 | :variadic (:variadic fexpr) 1385 | :max-fixed-arity (:max-fixed-arity fexpr) 1386 | :method-params (map :params (:methods fexpr)))] 1387 | [(assoc-in env [:locals name] be') 1388 | (conj bes be')])) 1389 | [meth-env []] bes) 1390 | expr (analyze (assoc meth-env :context (if (= :expr context) :return context)) `(do ~@exprs))] 1391 | {:env env :op :letfn :bindings bes :expr expr :form form 1392 | :children (conj (vec (map :init bes)) expr)})) 1393 | 1394 | (defn analyze-do-statements* [env exprs] 1395 | (seq (map #(analyze (assoc env :context :statement) %) (butlast exprs)))) 1396 | 1397 | (defn analyze-do-statements [env exprs] 1398 | (disallowing-recur (analyze-do-statements* env exprs))) 1399 | 1400 | (defmethod parse 'do 1401 | [op env [_ & exprs :as form] _ _] 1402 | (let [statements (analyze-do-statements env exprs)] 1403 | (if (<= (count exprs) 1) 1404 | (let [ret (analyze env (first exprs)) 1405 | children (conj (vec statements) ret)] 1406 | {:op :do 1407 | :env env 1408 | :form form 1409 | :statements statements :ret ret 1410 | :children children}) 1411 | (let [ret-env (if (= :statement (:context env)) 1412 | (assoc env :context :statement) 1413 | (assoc env :context :return)) 1414 | ret (analyze ret-env (last exprs)) 1415 | children (conj (vec statements) ret)] 1416 | {:op :do 1417 | :env env 1418 | :form form 1419 | :statements statements 1420 | :ret ret 1421 | :children children})))) 1422 | 1423 | (defn analyze-let-binding-init [env init loop-lets] 1424 | (binding [*loop-lets* loop-lets] 1425 | (analyze env init))) 1426 | 1427 | (defn get-let-tag [name init-expr] 1428 | (let [tag (-> name meta :tag)] 1429 | (if-not (nil? tag) 1430 | tag 1431 | (let [tag (-> init-expr :tag)] 1432 | (if-not (nil? tag) 1433 | tag 1434 | (-> init-expr :info :tag)))))) 1435 | 1436 | (defn analyze-let-bindings* [encl-env bindings] 1437 | (loop [bes [] 1438 | env (assoc encl-env :context :expr) 1439 | bindings (seq (partition 2 bindings))] 1440 | (let [binding (first bindings)] 1441 | (if-not (nil? binding) 1442 | (let [[name init] binding] 1443 | (when (or (not (nil? (namespace name))) 1444 | #?(:clj (.contains (str name) ".") 1445 | :cljs ^boolean (goog.string/contains (str name) "."))) 1446 | (throw (error encl-env (str "Invalid local name: " name)))) 1447 | (let [init-expr (analyze-let-binding-init env init (cons {:params bes} *loop-lets*)) 1448 | line (get-line name env) 1449 | col (get-col name env) 1450 | be {:name name 1451 | :line line 1452 | :column col 1453 | :init init-expr 1454 | :tag (get-let-tag name init-expr) 1455 | :local true 1456 | :shadow (-> env :locals name) 1457 | ;; Give let* bindings same shape as var so 1458 | ;; they get routed correctly in the compiler 1459 | :op :var 1460 | :env {:line line :column col} 1461 | :info {:name name 1462 | :shadow (-> env :locals name)} 1463 | :binding-form? true} 1464 | be (if (= :fn (:op init-expr)) 1465 | ;; TODO: can we simplify - David 1466 | (merge be 1467 | {:fn-var true 1468 | :variadic (:variadic init-expr) 1469 | :max-fixed-arity (:max-fixed-arity init-expr) 1470 | :method-params (map :params (:methods init-expr))}) 1471 | be)] 1472 | (recur (conj bes be) 1473 | (assoc-in env [:locals name] be) 1474 | (next bindings)))) 1475 | [bes env])))) 1476 | 1477 | (defn analyze-let-bindings [encl-env bindings] 1478 | (disallowing-recur (analyze-let-bindings* encl-env bindings))) 1479 | 1480 | (defn analyze-let-body* [env context exprs] 1481 | (analyze (assoc env :context (if (= :expr context) :return context)) `(do ~@exprs))) 1482 | 1483 | (defn analyze-let-body [env context exprs recur-frames loop-lets] 1484 | (binding [*recur-frames* recur-frames 1485 | *loop-lets* loop-lets] 1486 | (analyze-let-body* env context exprs))) 1487 | 1488 | (defn analyze-let 1489 | [encl-env [_ bindings & exprs :as form] is-loop] 1490 | (when-not (and (vector? bindings) (even? (count bindings))) 1491 | (throw (error encl-env "bindings must be vector of even number of elements"))) 1492 | (let [context (:context encl-env) 1493 | [bes env] (analyze-let-bindings encl-env bindings) 1494 | recur-frame (when (true? is-loop) 1495 | {:params bes :flag (atom nil)}) 1496 | recur-frames (if recur-frame 1497 | (cons recur-frame *recur-frames*) 1498 | *recur-frames*) 1499 | loop-lets (cond 1500 | (true? is-loop) *loop-lets* 1501 | (not (nil? *loop-lets*)) (cons {:params bes} *loop-lets*)) 1502 | expr (analyze-let-body env context exprs recur-frames loop-lets) 1503 | op (if (true? is-loop) :loop :let) 1504 | children (conj (vec (map :init bes)) expr)] 1505 | {:op op 1506 | :env encl-env 1507 | :bindings bes 1508 | :expr expr 1509 | :form form 1510 | :children children})) 1511 | 1512 | (defmethod parse 'let* 1513 | [op encl-env form _ _] 1514 | (analyze-let encl-env form false)) 1515 | 1516 | (defmethod parse 'loop* 1517 | [op encl-env form _ _] 1518 | (analyze-let encl-env form true)) 1519 | 1520 | (defmethod parse 'recur 1521 | [op env [_ & exprs :as form] _ _] 1522 | (let [context (:context env) 1523 | frame (first *recur-frames*) 1524 | exprs (disallowing-recur (vec (map #(analyze (assoc env :context :expr) %) exprs)))] 1525 | (when-not frame 1526 | (throw (error env "Can't recur here"))) 1527 | (when-not (= (count exprs) (count (:params frame))) 1528 | (throw (error env "recur argument count mismatch"))) 1529 | (reset! (:flag frame) true) 1530 | (assoc {:env env :op :recur :form form} 1531 | :frame frame 1532 | :exprs exprs 1533 | :children exprs))) 1534 | 1535 | (defmethod parse 'quote 1536 | [_ env [_ x] _ _] 1537 | (analyze (assoc env :quoted? true) x)) 1538 | 1539 | (defmethod parse 'new 1540 | [_ env [_ ctor & args :as form] _ _] 1541 | (when-not (symbol? ctor) 1542 | (throw (error env "First arg to new must be a symbol"))) 1543 | (disallowing-recur 1544 | (let [enve (assoc env :context :expr) 1545 | ctorexpr (analyze enve ctor) 1546 | ctor-var (resolve-existing-var env ctor) 1547 | record-args 1548 | (when (and (:record ctor-var) (not (-> ctor meta :internal-ctor))) 1549 | (repeat 3 (analyze enve nil))) 1550 | argexprs (into (vec (map #(analyze enve %) args)) record-args) 1551 | known-num-fields (:num-fields ctor-var) 1552 | argc (count args)] 1553 | (when (and (not (-> ctor meta :internal-ctor)) 1554 | known-num-fields (not= known-num-fields argc)) 1555 | (warning :fn-arity env {:argc argc :ctor ctor})) 1556 | {:env env :op :new :form form :ctor ctorexpr :args argexprs 1557 | :children (into [ctorexpr] argexprs) 1558 | :tag (let [name (-> ctorexpr :info :name)] 1559 | (or ('{js/Object object 1560 | js/String string 1561 | js/Array array 1562 | js/Number number 1563 | js/Function function 1564 | js/Boolean boolean} name) 1565 | name))}))) 1566 | 1567 | (defmethod parse 'set! 1568 | [_ env [_ target val alt :as form] _ _] 1569 | (let [[target val] (if alt 1570 | ;; (set! o -prop val) 1571 | [`(. ~target ~val) alt] 1572 | [target val])] 1573 | (disallowing-recur 1574 | (let [enve (assoc env :context :expr) 1575 | targetexpr (cond 1576 | (and 1577 | (= target '*unchecked-if*) ;; TODO: proper resolve 1578 | (or (true? val) (false? val))) 1579 | (do 1580 | #?(:clj (reset! *unchecked-if* val) 1581 | :cljs (set! *unchecked-if* val)) 1582 | ::set-unchecked-if) 1583 | 1584 | (symbol? target) 1585 | (do 1586 | (when (:const (resolve-var (dissoc env :locals) target)) 1587 | (throw (error env "Can't set! a constant"))) 1588 | (let [local (-> env :locals target)] 1589 | (when-not (or (nil? local) 1590 | (and (:field local) 1591 | (or (:mutable local) 1592 | (:unsynchronized-mutable local) 1593 | (:volatile-mutable local)))) 1594 | (throw (error env "Can't set! local var or non-mutable field")))) 1595 | (analyze-symbol enve target)) 1596 | 1597 | :else 1598 | (when (seq? target) 1599 | (let [targetexpr (analyze-seq enve target nil)] 1600 | (when (:field targetexpr) 1601 | targetexpr)))) 1602 | valexpr (analyze enve val)] 1603 | (when-not targetexpr 1604 | (throw (error env "set! target must be a field or a symbol naming a var"))) 1605 | (cond 1606 | (= targetexpr ::set-unchecked-if) {:env env :op :no-op} 1607 | :else {:env env :op :set! :form form :target targetexpr :val valexpr 1608 | :children [targetexpr valexpr]}))))) 1609 | 1610 | (declare analyze-file) 1611 | 1612 | #?(:clj 1613 | (defn locate-src 1614 | "Given a namespace return the corresponding ClojureScript (.cljs or .cljc) 1615 | resource on the classpath or file from the root of the build." 1616 | [ns] 1617 | (or (util/ns->source ns) 1618 | ;; Find sources available in inputs given to cljs.closure/build - Juho Teperi 1619 | (some (fn [source] 1620 | (if (= ns (:ns source)) 1621 | (:source-file source))) 1622 | (:sources @env/*compiler*)) 1623 | ;; Find sources in directory given to cljs.compiler/compile-root - Juho Teperi 1624 | (let [rootp (when-let [root (:root @env/*compiler*)] 1625 | (.getPath ^File root)) 1626 | cljsf (io/file rootp (ns->relpath ns :cljs)) 1627 | cljcf (io/file rootp (ns->relpath ns :cljc))] 1628 | (if (and (.exists cljsf) (.isFile cljsf)) 1629 | cljsf 1630 | (if (and (.exists cljcf) (.isFile cljcf)) 1631 | cljcf)))))) 1632 | 1633 | (defn foreign-dep? 1634 | #?(:cljs {:tag boolean}) 1635 | [dep] 1636 | {:pre [(symbol? dep)]} 1637 | (let [js-index (:js-dependency-index @env/*compiler*)] 1638 | (if-let [[_ {:keys [foreign]}] (find js-index (name dep))] 1639 | foreign 1640 | false))) 1641 | 1642 | (defn analyze-deps 1643 | "Given a lib, a namespace, deps, its dependencies, env, an analysis environment 1644 | and opts, compiler options - analyze all of the dependencies. Required to 1645 | correctly analyze usage of other namespaces." 1646 | ([lib deps env] (analyze-deps lib deps env nil)) 1647 | ([lib deps env opts] 1648 | (let [compiler @env/*compiler*] 1649 | (binding [*cljs-dep-set* (vary-meta (conj *cljs-dep-set* lib) update-in [:dep-path] conj lib)] 1650 | (assert (every? #(not (contains? *cljs-dep-set* %)) deps) 1651 | (str "Circular dependency detected " (-> *cljs-dep-set* meta :dep-path))) 1652 | (doseq [dep deps] 1653 | (when-not (or (not-empty (get-in compiler [::namespaces dep :defs])) 1654 | (contains? (:js-dependency-index compiler) (name dep)) 1655 | #?(:clj (deps/find-classpath-lib dep))) 1656 | #?(:clj (if-let [src (locate-src dep)] 1657 | (analyze-file src opts) 1658 | (throw 1659 | (error env 1660 | (error-message :undeclared-ns {:ns-sym dep :js-provide (name dep)})))) 1661 | :cljs (throw 1662 | (error env 1663 | (error-message :undeclared-ns {:ns-sym dep :js-provide (name dep)})))))))))) 1664 | 1665 | (defn check-uses [uses env] 1666 | (doseq [[sym lib] uses] 1667 | (let [js-lib (get-in @env/*compiler* [:js-dependency-index (name lib)])] 1668 | (when (and (= (get-in @env/*compiler* [::namespaces lib :defs sym] ::not-found) ::not-found) 1669 | (not (= (get js-lib :group) :goog)) 1670 | (not (get js-lib :closure-lib))) 1671 | (throw 1672 | (error env 1673 | (error-message :undeclared-ns-form {:type "var" :lib lib :sym sym}))))))) 1674 | 1675 | (defn check-use-macros [use-macros env] 1676 | (doseq [[sym lib] use-macros] 1677 | (let [the-ns #?(:clj (find-ns lib) 1678 | :cljs (find-macros-ns lib))] 1679 | (when (or (nil? the-ns) (nil? (.findInternedVar ^clojure.lang.Namespace the-ns sym))) 1680 | (throw 1681 | (error env 1682 | (error-message :undeclared-ns-form {:type "macro" :lib lib :sym sym}))))))) 1683 | 1684 | (defn parse-ns-error-msg [spec msg] 1685 | (str msg "; offending spec: " (pr-str spec))) 1686 | 1687 | (defn basic-validate-ns-spec [env macros? spec] 1688 | (when-not (or (symbol? spec) (sequential? spec)) 1689 | (throw 1690 | (error env 1691 | (parse-ns-error-msg spec 1692 | "Only [lib.ns & options] and lib.ns specs supported in :require / :require-macros")))) 1693 | (when (sequential? spec) 1694 | (when-not (symbol? (first spec)) 1695 | (throw 1696 | (error env 1697 | (parse-ns-error-msg spec 1698 | "Library name must be specified as a symbol in :require / :require-macros")))) 1699 | (when-not (odd? (count spec)) 1700 | (throw 1701 | (error env 1702 | (parse-ns-error-msg spec 1703 | "Only :as alias and :refer (names) options supported in :require")))) 1704 | (when-not (every? #{:as :refer} (map first (partition 2 (next spec)))) 1705 | (throw 1706 | (error env 1707 | (parse-ns-error-msg spec 1708 | "Only :as and :refer options supported in :require / :require-macros")))) 1709 | (when-not (let [fs (frequencies (next spec))] 1710 | (and (<= (fs :as 0) 1) 1711 | (<= (fs :refer 0) 1))) 1712 | (throw 1713 | (error env 1714 | (parse-ns-error-msg spec 1715 | "Each of :as and :refer options may only be specified once in :require / :require-macros")))))) 1716 | 1717 | (defn parse-ns-excludes [env args] 1718 | (reduce 1719 | (fn [s [k exclude xs]] 1720 | (if (= k :refer-clojure) 1721 | (do 1722 | (when-not (= exclude :exclude) 1723 | (throw (error env "Only [:refer-clojure :exclude (names)] form supported"))) 1724 | (when (seq s) 1725 | (throw (error env "Only one :refer-clojure form is allowed per namespace definition"))) 1726 | (into s xs)) 1727 | s)) 1728 | #{} args)) 1729 | 1730 | (defn use->require [env [lib kw referred :as spec]] 1731 | (when-not (and (symbol? lib) (= :only kw) (sequential? referred) (every? symbol? referred)) 1732 | (throw 1733 | (error env 1734 | (parse-ns-error-msg spec 1735 | "Only [lib.ns :only (names)] specs supported in :use / :use-macros")))) 1736 | [lib :refer referred]) 1737 | 1738 | (defn parse-require-spec [env macros? deps aliases spec] 1739 | (if (symbol? spec) 1740 | (recur env macros? deps aliases [spec]) 1741 | (do 1742 | (basic-validate-ns-spec env macros? spec) 1743 | (let [[lib & opts] spec 1744 | lib (if-let [js-module-name (get-in @env/*compiler* [:js-module-index (name lib)])] 1745 | (symbol js-module-name) 1746 | lib) 1747 | {alias :as referred :refer :or {alias lib}} (apply hash-map opts) 1748 | [rk uk] (if macros? [:require-macros :use-macros] [:require :use])] 1749 | (when-not (or (symbol? alias) (nil? alias)) 1750 | (throw 1751 | (error env 1752 | (parse-ns-error-msg spec 1753 | ":as must be followed by a symbol in :require / :require-macros")))) 1754 | (when alias 1755 | (let [alias-type (if macros? :macros :fns) 1756 | lib' ((alias-type @aliases) alias)] 1757 | (when (and (not (nil? lib')) (not= lib lib')) 1758 | (throw (error env (parse-ns-error-msg spec ":as alias must be unique")))) 1759 | (swap! aliases 1760 | update-in [alias-type] 1761 | conj [alias lib]))) 1762 | (when-not (or (and (sequential? referred) 1763 | (every? symbol? referred)) 1764 | (nil? referred)) 1765 | (throw 1766 | (error env 1767 | (parse-ns-error-msg spec 1768 | ":refer must be followed by a sequence of symbols in :require / :require-macros")))) 1769 | (when-not macros? 1770 | (swap! deps conj lib)) 1771 | (merge 1772 | (when alias 1773 | {rk (merge {alias lib} {lib lib})}) 1774 | (when referred {uk (apply hash-map (interleave referred (repeat lib)))})))))) 1775 | 1776 | (defn parse-import-spec [env deps spec] 1777 | (when-not (or (and (sequential? spec) 1778 | (every? symbol? spec)) 1779 | (and (symbol? spec) (nil? (namespace spec)))) 1780 | (throw (error env (parse-ns-error-msg spec "Only lib.ns.Ctor or [lib.ns Ctor*] spec supported in :import")))) 1781 | (let [import-map (if (sequential? spec) 1782 | (->> (rest spec) 1783 | (map #(vector % (symbol (str (first spec) "." %)))) 1784 | (into {})) 1785 | {(symbol (last (string/split (str spec) #"\."))) spec})] 1786 | (doseq [[_ spec] import-map] 1787 | (swap! deps conj spec)) 1788 | {:import import-map 1789 | :require import-map})) 1790 | 1791 | (declare parse-ns) 1792 | 1793 | (defn macro-autoload-ns? 1794 | "Given a spec form check whether the spec namespace requires a macro file 1795 | of the same name. If so return true." 1796 | #?(:cljs {:tag boolean}) 1797 | [form] 1798 | (when *macro-infer* 1799 | (let [ns (if (sequential? form) (first form) form) 1800 | {:keys [use-macros require-macros]} 1801 | (or (get-in @env/*compiler* [::namespaces ns]) 1802 | #?(:clj 1803 | (when-let [res (util/ns->source ns)] 1804 | (:ast (parse-ns res)))))] 1805 | (or (some #{ns} (vals use-macros)) 1806 | (some #{ns} (vals require-macros)))))) 1807 | 1808 | (defn desugar-ns-specs 1809 | "Given an original set of ns specs desugar :include-macros and :refer-macros 1810 | usage into only primitive spec forms - :use, :require, :use-macros, 1811 | :require-macros. If a library includes a macro file of with the same name 1812 | as the namespace will also be desugared." 1813 | [args] 1814 | (let [{:keys [require] :as indexed} 1815 | (->> args 1816 | (map (fn [[k & specs]] [k (into [] specs)])) 1817 | (into {})) 1818 | sugar-keys #{:include-macros :refer-macros} 1819 | remove-from-spec 1820 | (fn [pred spec] 1821 | (if-not (and (sequential? spec) (some pred spec)) 1822 | spec 1823 | (let [[l r] (split-with (complement pred) spec)] 1824 | (recur pred (concat l (drop 2 r)))))) 1825 | replace-refer-macros 1826 | (fn [spec] 1827 | (if-not (sequential? spec) 1828 | spec 1829 | (map (fn [x] (if (= x :refer-macros) :refer x)) spec))) 1830 | reload-spec? #(#{:reload :reload-all} %) 1831 | to-macro-specs 1832 | (fn [specs] 1833 | (->> specs 1834 | (filter 1835 | (fn [x] 1836 | (or (and (sequential? x) 1837 | (some sugar-keys x)) 1838 | (reload-spec? x) 1839 | (macro-autoload-ns? x)))) 1840 | (map (fn [x] 1841 | (if-not (reload-spec? x) 1842 | (->> x (remove-from-spec #{:include-macros}) 1843 | (remove-from-spec #{:refer}) 1844 | (replace-refer-macros)) 1845 | x))))) 1846 | remove-sugar (partial remove-from-spec sugar-keys)] 1847 | (if-let [require-specs (seq (to-macro-specs require))] 1848 | (map (fn [x] 1849 | (if-not (reload-spec? x) 1850 | (let [[k v] x] 1851 | (cons k (map remove-sugar v))) 1852 | x)) 1853 | (update-in indexed [:require-macros] (fnil into []) require-specs)) 1854 | args))) 1855 | 1856 | (defn find-def-clash [env ns segments] 1857 | (let [to-check (map (fn [xs] 1858 | [(symbol (string/join "." (butlast xs))) 1859 | (symbol (last xs))]) 1860 | (drop 2 (reductions conj [] segments)))] 1861 | (doseq [[clash-ns name] to-check] 1862 | (when (get-in @env/*compiler* [::namespaces clash-ns :defs name]) 1863 | (warning :ns-var-clash env 1864 | {:ns ns 1865 | :var (symbol (str clash-ns) (str name))}))))) 1866 | 1867 | (defn macro-ns-name [name] 1868 | (let [name-str (str name)] 1869 | (if-not #?(:clj (.endsWith name-str "$macros") 1870 | :cljs (gstring/endsWith name-str "$macros")) 1871 | (symbol (str name-str "$macros")) 1872 | name))) 1873 | 1874 | (defmethod parse 'ns 1875 | [_ env [_ name & args :as form] _ opts] 1876 | (when-not (symbol? name) 1877 | (throw (error env "Namespaces must be named by a symbol."))) 1878 | (let [name (cond-> name (:macros-ns opts) macro-ns-name)] 1879 | (let [segments (string/split (clojure.core/name name) #"\.")] 1880 | (when (= 1 (count segments)) 1881 | (warning :single-segment-namespace env {:name name})) 1882 | (when (some js-reserved segments) 1883 | (warning :munged-namespace env {:name name})) 1884 | (find-def-clash env name segments) 1885 | #?(:clj 1886 | (when (some (complement util/valid-js-id-start?) segments) 1887 | (throw 1888 | (AssertionError. 1889 | (str "Namespace " name " has a segment starting with an invaild " 1890 | "JavaScript identifier")))))) 1891 | (let [docstring (if (string? (first args)) (first args)) 1892 | mdocstr (-> name meta :doc) 1893 | args (if docstring (next args) args) 1894 | metadata (if (map? (first args)) (first args)) 1895 | form-meta (meta form) 1896 | args (desugar-ns-specs (if metadata (next args) args)) 1897 | name (vary-meta name merge metadata) 1898 | excludes (parse-ns-excludes env args) 1899 | deps (atom #{}) 1900 | aliases (atom {:fns {} :macros {}}) 1901 | spec-parsers {:require (partial parse-require-spec env false deps aliases) 1902 | :require-macros (partial parse-require-spec env true deps aliases) 1903 | :use (comp (partial parse-require-spec env false deps aliases) 1904 | (partial use->require env)) 1905 | :use-macros (comp (partial parse-require-spec env true deps aliases) 1906 | (partial use->require env)) 1907 | :import (partial parse-import-spec env deps)} 1908 | valid-forms (atom #{:use :use-macros :require :require-macros :import}) 1909 | reload (atom {:use nil :require nil :use-macros nil :require-macros nil}) 1910 | reloads (atom {}) 1911 | {uses :use requires :require use-macros :use-macros require-macros :require-macros imports :import :as params} 1912 | (reduce 1913 | (fn [m [k & libs]] 1914 | (when-not (#{:use :use-macros :require :require-macros :import} k) 1915 | (throw (error env "Only :refer-clojure, :require, :require-macros, :use, :use-macros, and :import libspecs supported"))) 1916 | (when-not (@valid-forms k) 1917 | (throw (error env (str "Only one " k " form is allowed per namespace definition")))) 1918 | (swap! valid-forms disj k) 1919 | ;; check for spec type reloads 1920 | (when-not (= :import k) 1921 | (when (some #{:reload} libs) 1922 | (swap! reload assoc k :reload)) 1923 | (when (some #{:reload-all} libs) 1924 | (swap! reload assoc k :reload-all))) 1925 | ;; check for individual ns reloads from REPL interactions 1926 | (when-let [xs (seq (filter #(-> % meta :reload) libs))] 1927 | (swap! reloads assoc k 1928 | (zipmap (map first xs) (map #(-> % meta :reload) xs)))) 1929 | (apply merge-with merge m 1930 | (map (spec-parsers k) 1931 | (remove #{:reload :reload-all} libs)))) 1932 | {} (remove (fn [[r]] (= r :refer-clojure)) args))] 1933 | (set! *cljs-ns* name) 1934 | (let [ns-info 1935 | {:name name 1936 | :doc (or docstring mdocstr) 1937 | :excludes excludes 1938 | :use-macros use-macros 1939 | :require-macros require-macros 1940 | :uses uses 1941 | :requires requires 1942 | :imports imports} 1943 | ns-info 1944 | (if (:merge form-meta) 1945 | ;; for merging information in via require usage in REPLs 1946 | (let [ns-info' (get-in @env/*compiler* [::namespaces name])] 1947 | (if (pos? (count ns-info')) 1948 | (let [merge-keys 1949 | [:use-macros :require-macros :uses :requires :imports]] 1950 | (merge 1951 | ns-info' 1952 | (merge-with merge 1953 | (select-keys ns-info' merge-keys) 1954 | (select-keys ns-info merge-keys)))) 1955 | ns-info)) 1956 | ns-info)] 1957 | (swap! env/*compiler* update-in [::namespaces name] merge ns-info) 1958 | (merge {:op :ns 1959 | :env env 1960 | :form form 1961 | :deps @deps 1962 | :reload @reload 1963 | :reloads @reloads} 1964 | (cond-> ns-info 1965 | (@reload :use) 1966 | (update-in [:uses] 1967 | (fn [m] (with-meta m {(@reload :use) true}))) 1968 | (@reload :require) 1969 | (update-in [:requires] 1970 | (fn [m] (with-meta m {(@reload :require) true}))))))))) 1971 | 1972 | (defn parse-type 1973 | [op env [_ tsym fields pmasks body :as form]] 1974 | (let [t (:name (resolve-var (dissoc env :locals) tsym)) 1975 | locals (reduce (fn [m fld] 1976 | (assoc m fld 1977 | {:name fld 1978 | :line (get-line fld env) 1979 | :column (get-col fld env) 1980 | :field true 1981 | :mutable (-> fld meta :mutable) 1982 | :unsynchronized-mutable (-> fld meta :unsynchronized-mutable) 1983 | :volatile-mutable (-> fld meta :volatile-mutable) 1984 | :tag (-> fld meta :tag) 1985 | :shadow (m fld)})) 1986 | {} (if (= :defrecord* op) 1987 | (concat fields '[__meta __extmap ^:mutable __hash]) 1988 | fields)) 1989 | protocols (-> tsym meta :protocols)] 1990 | (swap! env/*compiler* update-in [::namespaces (-> env :ns :name) :defs tsym] 1991 | (fn [m] 1992 | (let [m (assoc (or m {}) 1993 | :name t 1994 | :type true 1995 | :num-fields (count fields) 1996 | :record (= :defrecord* op))] 1997 | (merge m 1998 | (dissoc (meta tsym) :protocols) 1999 | {:protocols protocols} 2000 | (source-info tsym env))))) 2001 | {:op op :env env :form form :t t :fields fields :pmasks pmasks 2002 | :protocols (disj protocols 'cljs.core/Object) 2003 | :body (analyze (assoc env :locals locals) body)})) 2004 | 2005 | (defmethod parse 'deftype* 2006 | [_ env form _ _] 2007 | (parse-type :deftype* env form)) 2008 | 2009 | (defmethod parse 'defrecord* 2010 | [_ env form _ _] 2011 | (parse-type :defrecord* env form) ) 2012 | 2013 | ;; dot accessor code 2014 | 2015 | (def ^:private property-symbol? #(boolean (and (symbol? %) (re-matches #"^-.*" (name %))))) 2016 | 2017 | (defn- classify-dot-form 2018 | [[target member args]] 2019 | [(cond (nil? target) ::error 2020 | :default ::expr) 2021 | (cond (property-symbol? member) ::property 2022 | (symbol? member) ::symbol 2023 | (seq? member) ::list 2024 | :default ::error) 2025 | (cond (nil? args) () 2026 | :default ::expr)]) 2027 | 2028 | (defmulti build-dot-form #(classify-dot-form %)) 2029 | 2030 | ;; (. o -p) 2031 | ;; (. (...) -p) 2032 | (defmethod build-dot-form [::expr ::property ()] 2033 | [[target prop _]] 2034 | {:dot-action ::access :target target :field (-> prop name (.substring 1) symbol)}) 2035 | 2036 | ;; (. o -p ) 2037 | (defmethod build-dot-form [::expr ::property ::list] 2038 | [[target prop args]] 2039 | #?(:clj (throw (Error. (str "Cannot provide arguments " args " on property access " prop))) 2040 | :cljs (throw (js/Error. (str "Cannot provide arguments " args " on property access " prop))))) 2041 | 2042 | (defn- build-method-call 2043 | "Builds the intermediate method call map used to reason about the parsed form during 2044 | compilation." 2045 | [target meth args] 2046 | (if (symbol? meth) 2047 | {:dot-action ::call :target target :method meth :args args} 2048 | {:dot-action ::call :target target :method (first meth) :args args})) 2049 | 2050 | ;; (. o m 1 2) 2051 | (defmethod build-dot-form [::expr ::symbol ::expr] 2052 | [[target meth args]] 2053 | (build-method-call target meth args)) 2054 | 2055 | ;; (. o m) 2056 | (defmethod build-dot-form [::expr ::symbol ()] 2057 | [[target meth args]] 2058 | (build-method-call target meth args)) 2059 | 2060 | ;; (. o (m)) 2061 | ;; (. o (m 1 2)) 2062 | (defmethod build-dot-form [::expr ::list ()] 2063 | [[target meth-expr _]] 2064 | (build-method-call target (first meth-expr) (rest meth-expr))) 2065 | 2066 | (defmethod build-dot-form :default 2067 | [dot-form] 2068 | #?(:clj (throw 2069 | (Error. 2070 | (str "Unknown dot form of " 2071 | (list* '. dot-form) " with classification " 2072 | (classify-dot-form dot-form)))) 2073 | :cljs (throw 2074 | (js/Error. 2075 | (str "Unknown dot form of " 2076 | (list* '. dot-form) " with classification " 2077 | (classify-dot-form dot-form)))))) 2078 | 2079 | (defn analyze-dot [env target field member+ form] 2080 | (let [v [target field member+] 2081 | {:keys [dot-action target method field args]} (build-dot-form v) 2082 | enve (assoc env :context :expr) 2083 | targetexpr (analyze enve target) 2084 | form-meta (meta form) 2085 | tag (:tag form-meta)] 2086 | (case dot-action 2087 | ::access (let [children [targetexpr]] 2088 | {:op :dot 2089 | :env env 2090 | :form form 2091 | :target targetexpr 2092 | :field field 2093 | :children children 2094 | :tag tag}) 2095 | ::call (let [argexprs (map #(analyze enve %) args) 2096 | children (into [targetexpr] argexprs)] 2097 | {:op :dot 2098 | :env env 2099 | :form form 2100 | :target targetexpr 2101 | :method method 2102 | :args argexprs 2103 | :children children 2104 | :tag tag})))) 2105 | 2106 | (defmethod parse '. 2107 | [_ env [_ target & [field & member+] :as form] _ _] 2108 | (disallowing-recur (analyze-dot env target field member+ form))) 2109 | 2110 | (defn get-js-tag [form] 2111 | (let [form-meta (meta form) 2112 | tag (:tag form-meta)] 2113 | (if-not (nil? tag) 2114 | tag 2115 | (when (true? (:numeric form-meta)) 2116 | 'number)))) 2117 | 2118 | (defn js-star-interp 2119 | [env ^String s] 2120 | (let [idx (.indexOf s "~{")] 2121 | (if (== -1 idx) 2122 | (list s) 2123 | (let [end (.indexOf s "}" idx) 2124 | inner (:name (resolve-existing-var env (symbol (subs s (+ 2 idx) end))))] 2125 | (lazy-seq 2126 | (cons (subs s 0 idx) 2127 | (cons inner 2128 | (js-star-interp env (subs s (inc end)))))))))) 2129 | 2130 | (defn js-star-seg 2131 | [^String s] 2132 | (let [idx (.indexOf s "~{")] 2133 | (if (== -1 idx) 2134 | (list s) 2135 | (let [end (.indexOf s "}" idx)] 2136 | (lazy-seq 2137 | (cons (subs s 0 idx) 2138 | (js-star-seg (subs s (inc end))))))))) 2139 | 2140 | (def NUMERIC_SET '#{any number long double}) 2141 | 2142 | (defn numeric-type? 2143 | #?(:cljs {:tag boolean}) 2144 | [t] 2145 | ;; TODO: type inference is not strong enough to detect that 2146 | ;; when functions like first won't return nil, so variadic 2147 | ;; numeric functions like cljs.core/< would produce a spurious 2148 | ;; warning without this - David 2149 | (if (nil? t) 2150 | true 2151 | (if (and (symbol? t) (not (nil? (get NUMERIC_SET t)))) 2152 | true 2153 | (when #?(:clj (set? t) 2154 | :cljs (cljs-set? t)) 2155 | (or (contains? t 'number) 2156 | (contains? t 'long) 2157 | (contains? t 'double) 2158 | (contains? t 'any)))))) 2159 | 2160 | (defn analyze-js-star* [env jsform args form] 2161 | (let [enve (assoc env :context :expr) 2162 | argexprs (vec (map #(analyze enve %) args)) 2163 | form-meta (meta form) 2164 | segs (js-star-seg jsform) 2165 | tag (get-js-tag form) 2166 | js-op (:js-op form-meta) 2167 | numeric (:numeric form-meta)] 2168 | (when (true? numeric) 2169 | (let [types (map #(infer-tag env %) argexprs)] 2170 | (when-not (every? numeric-type? types) 2171 | (warning :invalid-arithmetic env 2172 | {:js-op js-op 2173 | :types (into [] types)})))) 2174 | {:op :js 2175 | :env env 2176 | :segs segs 2177 | :args argexprs 2178 | :tag tag 2179 | :form form 2180 | :children argexprs 2181 | :js-op js-op 2182 | :numeric numeric})) 2183 | 2184 | (defn analyze-js-star [env jsform args form] 2185 | (disallowing-recur (analyze-js-star* env jsform args form))) 2186 | 2187 | (defmethod parse 'js* 2188 | [op env [_ jsform & args :as form] _ _] 2189 | (when-not (string? jsform) 2190 | (throw (error env "Invalid js* form"))) 2191 | (if-not (nil? args) 2192 | (analyze-js-star env jsform args form) 2193 | (let [code (apply str (js-star-interp env jsform)) 2194 | tag (get-js-tag form) 2195 | form-meta (meta form) 2196 | js-op (:js-op form-meta) 2197 | numeric (:numeric form-meta)] 2198 | {:op :js 2199 | :env env 2200 | :form form 2201 | :code code 2202 | :tag tag 2203 | :js-op js-op 2204 | :numeric numeric}))) 2205 | 2206 | (defn- analyzed? 2207 | #?(:cljs {:tag boolean}) 2208 | [f] 2209 | (contains? (meta f) ::analyzed)) 2210 | 2211 | (defn- all-values? 2212 | #?(:cljs {:tag boolean}) 2213 | [exprs] 2214 | (every? #{:var :constant} (map :op exprs))) 2215 | 2216 | (defn- valid-arity? 2217 | #?(:cljs {:tag boolean}) 2218 | [argc method-params] 2219 | (boolean (some #{argc} (map count method-params)))) 2220 | 2221 | (defn parse-invoke* 2222 | [env [f & args :as form]] 2223 | (let [enve (assoc env :context :expr) 2224 | fexpr (analyze enve f) 2225 | argc (count args) 2226 | fn-var? (-> fexpr :info :fn-var) 2227 | kw? (= 'cljs.core/Keyword (:tag fexpr))] 2228 | (when ^boolean fn-var? 2229 | (let [{:keys [^boolean variadic max-fixed-arity method-params name]} (:info fexpr)] 2230 | (when (and (not (valid-arity? argc method-params)) 2231 | (or (not variadic) 2232 | (and variadic (< argc max-fixed-arity)))) 2233 | (warning :fn-arity env {:name name :argc argc})))) 2234 | (when (and kw? (not (or (== 1 argc) (== 2 argc)))) 2235 | (warning :fn-arity env {:name (first form) :argc argc})) 2236 | (let [deprecated? (-> fexpr :info :deprecated) 2237 | no-warn? (-> form meta :deprecation-nowarn)] 2238 | (when (and (boolean deprecated?) 2239 | (not (boolean no-warn?))) 2240 | (warning :fn-deprecated env {:fexpr fexpr}))) 2241 | (when-not (nil? (-> fexpr :info :type)) 2242 | (warning :invoke-ctor env {:fexpr fexpr})) 2243 | (let [ana-expr #(analyze enve %) 2244 | argexprs (map ana-expr args)] 2245 | (if (or (not (boolean *cljs-static-fns*)) 2246 | (not (symbol? f)) 2247 | fn-var? 2248 | (analyzed? f) 2249 | (all-values? argexprs)) 2250 | {:env env :op :invoke :form form :f fexpr :args (vec argexprs) 2251 | :children (into [fexpr] argexprs)} 2252 | (let [arg-syms (take argc (repeatedly gensym))] 2253 | (analyze env 2254 | `(let [~@(vec (interleave arg-syms args))] 2255 | (~(vary-meta f assoc ::analyzed true) ~@arg-syms)))))))) 2256 | 2257 | (defn parse-invoke 2258 | [env form] 2259 | (disallowing-recur (parse-invoke* env form))) 2260 | 2261 | (defn analyze-symbol 2262 | "Finds the var associated with sym" 2263 | [env sym] 2264 | (if ^boolean (:quoted? env) 2265 | (do 2266 | (register-constant! env sym) 2267 | (analyze-wrap-meta {:op :constant :env env :form sym :tag 'cljs.core/Symbol})) 2268 | (let [{:keys [line column]} (meta sym) 2269 | env (if-not (nil? line) 2270 | (assoc env :line line) 2271 | env) 2272 | env (if-not (nil? column) 2273 | (assoc env :column column) 2274 | env) 2275 | ret {:env env :form sym} 2276 | lcls (:locals env) 2277 | lb (get lcls sym)] 2278 | (if-not (nil? lb) 2279 | (assoc ret :op :var :info lb) 2280 | (if-not (true? (:def-var env)) 2281 | (let [sym-meta (meta sym) 2282 | info (if-not (contains? sym-meta ::analyzed) 2283 | (resolve-existing-var env sym) 2284 | (resolve-var env sym))] 2285 | (assoc ret :op :var :info info)) 2286 | (let [info (resolve-var env sym)] 2287 | (assoc ret :op :var :info info))))))) 2288 | 2289 | (defn excluded? 2290 | #?(:cljs {:tag boolean}) 2291 | [env sym] 2292 | (if-not (nil? (gets env :ns :excludes sym)) 2293 | true 2294 | (not (nil? (gets @env/*compiler* ::namespaces (gets env :ns :name) :excludes sym))))) 2295 | 2296 | (defn used? 2297 | #?(:cljs {:tag boolean}) 2298 | [env sym] 2299 | (if-not (nil? (gets env :ns :use-macros sym)) 2300 | true 2301 | (not (nil? (gets @env/*compiler* ::namespaces (gets env :ns :name) :use-macros sym))))) 2302 | 2303 | (defn get-expander-ns [env ^String nstr] 2304 | (cond 2305 | #?@(:clj [(= "clojure.core" nstr) (find-ns 'cljs.core)] 2306 | :cljs [(identical? "clojure.core" nstr) (find-macros-ns CLJS_CORE_MACROS_SYM)]) 2307 | #?@(:clj [(= "clojure.repl" nstr) (find-ns 'cljs.repl)] 2308 | :cljs [(identical? "clojure.repl" nstr) (find-macros-ns 'cljs.repl)]) 2309 | #?@(:clj [(.contains nstr ".") (find-ns (symbol nstr))] 2310 | :cljs [(goog.string/contains nstr ".") (find-macros-ns (symbol nstr))]) 2311 | :else (some-> env :ns :require-macros (get (symbol nstr)) find-ns))) 2312 | 2313 | (defn get-expander* [sym env] 2314 | (when-not (or (not (nil? (gets env :locals sym))) ; locals hide macros 2315 | (and (excluded? env sym) (not (used? env sym)))) 2316 | (let [nstr (namespace sym)] 2317 | (if-not (nil? nstr) 2318 | (let [ns (get-expander-ns env nstr)] 2319 | (when-not (nil? ns) 2320 | (.findInternedVar ^clojure.lang.Namespace ns (symbol (name sym))))) 2321 | (let [nsym (gets env :ns :use-macros sym)] 2322 | (if-not (nil? nsym) 2323 | (.findInternedVar ^clojure.lang.Namespace 2324 | #?(:clj (find-ns nsym) :cljs (find-macros-ns nsym)) sym) 2325 | (.findInternedVar ^clojure.lang.Namespace 2326 | #?(:clj (find-ns 'cljs.core) :cljs (find-macros-ns CLJS_CORE_MACROS_SYM)) sym))))))) 2327 | 2328 | (defn get-expander 2329 | "Given a sym, a symbol identifying a macro, and env, an analysis environment 2330 | return the corresponding Clojure macroexpander." 2331 | [sym env] 2332 | (let [mvar (get-expander* sym env)] 2333 | (when (and (not (nil? mvar)) 2334 | #?(:clj (.isMacro ^clojure.lang.Var mvar) 2335 | :cljs ^boolean (.isMacro mvar))) 2336 | mvar))) 2337 | 2338 | (defn macroexpand-1* 2339 | [env form] 2340 | (let [op (first form)] 2341 | (if-not (nil? (get specials op)) 2342 | form 2343 | (let [mac-var (when (symbol? op) (get-expander op env))] 2344 | (if-not (nil? mac-var) 2345 | (#?@(:clj [binding [*ns* (create-ns *cljs-ns*)]] 2346 | :cljs [do]) 2347 | (let [form' (apply @mac-var form env (rest form))] 2348 | (if #?(:clj (seq? form') :cljs (cljs-seq? form')) 2349 | (let [sym' (first form') 2350 | sym (first form)] 2351 | (if #?(:clj (= sym' 'js*) 2352 | :cljs (symbol-identical? sym' JS_STAR_SYM)) 2353 | (let [sym (if (namespace sym) 2354 | sym 2355 | (symbol "cljs.core" (str sym))) 2356 | js-op {:js-op sym} 2357 | js-op (if (true? (-> mac-var meta ::numeric)) 2358 | (assoc js-op :numeric true) 2359 | js-op)] 2360 | (vary-meta form' merge js-op)) 2361 | form')) 2362 | form'))) 2363 | (if (symbol? op) 2364 | (let [opname (str op)] 2365 | (cond 2366 | (identical? \. 2367 | #?(:clj (first opname) 2368 | :cljs (.charAt opname 0))) 2369 | (let [[target & args] (next form)] 2370 | (with-meta (list* #?(:clj '. :cljs DOT_SYM) target (symbol (subs opname 1)) args) 2371 | (meta form))) 2372 | 2373 | (identical? \. 2374 | #?(:clj (last opname) 2375 | :cljs (.charAt opname (dec (. opname -length))))) 2376 | (with-meta 2377 | (list* #?(:clj 'new :cljs NEW_SYM) (symbol (subs opname 0 (dec (count opname)))) (next form)) 2378 | (meta form)) 2379 | 2380 | :else form)) 2381 | form)))))) 2382 | 2383 | (defn macroexpand-1 2384 | "Given a env, an analysis environment, and form, a ClojureScript form, 2385 | macroexpand the form once." 2386 | [env form] 2387 | (ensure (wrapping-errors env (macroexpand-1* env form)))) 2388 | 2389 | (declare analyze-list) 2390 | 2391 | (defn analyze-seq* [op env form name opts] 2392 | (if-not (nil? (get specials op)) 2393 | (parse op env form name opts) 2394 | (parse-invoke env form))) 2395 | 2396 | (defn analyze-seq*-wrap [op env form name opts] 2397 | (wrapping-errors env 2398 | (analyze-seq* op env form name opts))) 2399 | 2400 | (defn analyze-seq 2401 | ([env form name] (analyze-seq env form name nil)) 2402 | ([env form name opts] 2403 | (if ^boolean (:quoted? env) 2404 | (analyze-list env form) 2405 | (let [line (-> form meta :line) 2406 | line (if (nil? line) 2407 | (:line env) 2408 | line) 2409 | col (-> form meta :column) 2410 | col (if (nil? col) 2411 | (:column env) 2412 | col) 2413 | env (assoc env :line line :column col)] 2414 | (let [op (first form)] 2415 | (when (nil? op) 2416 | (throw (error env "Can't call nil"))) 2417 | (let [mform (macroexpand-1 env form)] 2418 | (if (identical? form mform) 2419 | (analyze-seq*-wrap op env form name opts) 2420 | (analyze env mform name opts)))))))) 2421 | 2422 | (defn analyze-map 2423 | [env form] 2424 | (let [expr-env (assoc env :context :expr) 2425 | ks (disallowing-recur (vec (map #(analyze expr-env %) (keys form)))) 2426 | vs (disallowing-recur (vec (map #(analyze expr-env %) (vals form))))] 2427 | (analyze-wrap-meta {:op :map :env env :form form 2428 | :keys ks :vals vs 2429 | :children (vec (interleave ks vs)) 2430 | :tag 'cljs.core/IMap}))) 2431 | 2432 | (defn analyze-list 2433 | [env form] 2434 | (let [expr-env (assoc env :context :expr) 2435 | items (disallowing-recur (doall (map #(analyze expr-env %) form)))] 2436 | (analyze-wrap-meta {:op :list :env env :form form :items items :children items :tag 'cljs.core/IList}))) 2437 | 2438 | (defn analyze-vector 2439 | [env form] 2440 | (let [expr-env (assoc env :context :expr) 2441 | items (disallowing-recur (vec (map #(analyze expr-env %) form)))] 2442 | (analyze-wrap-meta {:op :vector :env env :form form :items items :children items :tag 'cljs.core/IVector}))) 2443 | 2444 | (defn analyze-set 2445 | [env form ] 2446 | (let [expr-env (assoc env :context :expr) 2447 | items (disallowing-recur (vec (map #(analyze expr-env %) form)))] 2448 | (analyze-wrap-meta {:op :set :env env :form form :items items :children items :tag 'cljs.core/ISet}))) 2449 | 2450 | (defn analyze-js-value 2451 | [env ^JSValue form] 2452 | (let [val (.-val form) 2453 | expr-env (assoc env :context :expr) 2454 | items (if (map? val) 2455 | (zipmap (keys val) 2456 | (disallowing-recur (doall (map #(analyze expr-env %) (vals val))))) 2457 | (disallowing-recur (doall (map #(analyze expr-env %) val))))] 2458 | {:op :js-value 2459 | :js-type (if (map? val) :object :array) 2460 | :env env 2461 | :form form 2462 | :items items 2463 | :children items 2464 | :tag (if (map? val) 'object 'array)})) 2465 | 2466 | (defn elide-reader-meta [m] 2467 | (dissoc m :file :line :column :end-column :end-line :source)) 2468 | 2469 | (defn analyze-wrap-meta [expr] 2470 | (let [form (:form expr) 2471 | m (elide-reader-meta (meta form))] 2472 | (if (seq m) 2473 | (let [env (:env expr) ; take on expr's context ourselves 2474 | expr (assoc-in expr [:env :context] :expr) ; change expr to :expr 2475 | meta-expr (analyze-map (:env expr) m)] 2476 | {:op :meta :env env :form form 2477 | :meta meta-expr :expr expr :children [meta-expr expr]}) 2478 | expr))) 2479 | 2480 | (defn infer-type [env ast _] 2481 | (let [tag (:tag ast)] 2482 | (if (nil? tag) 2483 | (let [tag (infer-tag env ast)] 2484 | (if-not (nil? tag) 2485 | (assoc ast :tag tag) 2486 | ast)) 2487 | ast))) 2488 | 2489 | #?(:clj 2490 | (defn ns-side-effects 2491 | [env {:keys [op] :as ast} opts] 2492 | (if (= :ns op) 2493 | (let [{:keys [deps uses require-macros use-macros reload reloads]} ast] 2494 | (when (and *analyze-deps* (seq deps)) 2495 | (analyze-deps name deps env (dissoc opts :macros-ns))) 2496 | (when (and *analyze-deps* (seq uses)) 2497 | (check-uses uses env)) 2498 | (when *load-macros* 2499 | (load-core) 2500 | (doseq [nsym (vals use-macros)] 2501 | (let [k (or (:use-macros reload) 2502 | (get-in reloads [:use-macros nsym]) 2503 | (and (= nsym name) *reload-macros* :reload))] 2504 | (if k 2505 | (clojure.core/require nsym k) 2506 | (clojure.core/require nsym)) 2507 | (intern-macros nsym k))) 2508 | (doseq [nsym (vals require-macros)] 2509 | (let [k (or (:require-macros reload) 2510 | (get-in reloads [:require-macros nsym]) 2511 | (and (= nsym name) *reload-macros* :reload))] 2512 | (if k 2513 | (clojure.core/require nsym k) 2514 | (clojure.core/require nsym)) 2515 | (intern-macros nsym k))) 2516 | (when (seq use-macros) 2517 | (check-use-macros use-macros env))) 2518 | ast) 2519 | ast))) 2520 | 2521 | (def ^:dynamic *passes* nil) 2522 | 2523 | #?(:clj 2524 | (defn analyze-form [env form name opts] 2525 | (load-core) 2526 | (cond 2527 | (symbol? form) (analyze-symbol env form) 2528 | (and (seq? form) (seq form)) (analyze-seq env form name opts) 2529 | (map? form) (analyze-map env form) 2530 | (vector? form) (analyze-vector env form) 2531 | (set? form) (analyze-set env form) 2532 | (keyword? form) (analyze-keyword env form) 2533 | (instance? JSValue form) (analyze-js-value env form) 2534 | (= () form) (analyze-list env form) 2535 | :else 2536 | (let [tag (cond 2537 | (nil? form) 'clj-nil 2538 | (number? form) 'number 2539 | (string? form) 'string 2540 | (true? form) 'boolean 2541 | (false? form) 'boolean)] 2542 | (cond-> {:op :constant :env env :form form} 2543 | tag (assoc :tag tag)))))) 2544 | 2545 | #?(:cljs 2546 | (defn analyze-form [env form name opts] 2547 | (cond 2548 | (symbol? form) (analyze-symbol env form) 2549 | (and (cljs-seq? form) (seq form)) (analyze-seq env form name opts) 2550 | (cljs-map? form) (analyze-map env form) 2551 | (cljs-vector? form) (analyze-vector env form) 2552 | (cljs-set? form) (analyze-set env form) 2553 | (keyword? form) (analyze-keyword env form) 2554 | (instance? cljs.tagged-literals/JSValue form) (analyze-js-value env form) 2555 | (= () form) (analyze-list env form) 2556 | :else 2557 | (let [tag (cond 2558 | (nil? form) CLJ_NIL_SYM 2559 | (number? form) NUMBER_SYM 2560 | (string? form) STRING_SYM 2561 | (true? form) BOOLEAN_SYM 2562 | (false? form) BOOLEAN_SYM)] 2563 | (cond-> {:op :constant :env env :form form} 2564 | tag (assoc :tag tag)))))) 2565 | 2566 | (defn analyze* [env form name opts] 2567 | (let [passes *passes* 2568 | passes (if (nil? passes) 2569 | #?(:clj [infer-type ns-side-effects] 2570 | :cljs [infer-type]) 2571 | passes) 2572 | form (if (instance? LazySeq form) 2573 | (if (seq form) form ()) 2574 | form) 2575 | ast (analyze-form env form name opts)] 2576 | (reduce (fn [ast pass] (pass env ast opts)) ast passes))) 2577 | 2578 | (defn analyze 2579 | "Given an environment, a map containing {:locals (mapping of names to bindings), :context 2580 | (one of :statement, :expr, :return), :ns (a symbol naming the 2581 | compilation ns)}, and form, returns an expression object (a map 2582 | containing at least :form, :op and :env keys). If expr has any (immediately) 2583 | nested exprs, must have :children [exprs...] entry. This will 2584 | facilitate code walking without knowing the details of the op set." 2585 | ([env form] (analyze env form nil)) 2586 | ([env form name] (analyze env form name nil)) 2587 | ([env form name opts] 2588 | (ensure 2589 | (wrapping-errors env 2590 | (binding [reader/*alias-map* (or reader/*alias-map* {})] 2591 | (analyze* env form name opts)))))) 2592 | 2593 | #?(:clj 2594 | (defn- source-path 2595 | "Returns a path suitable for providing to tools.reader as a 'filename'." 2596 | [x] 2597 | (cond 2598 | (instance? File x) (.getAbsolutePath ^File x) 2599 | :default (str x)))) 2600 | 2601 | (defn resolve-symbol [s] 2602 | (:name (resolve-var {:ns {:name *cljs-ns*}} s))) 2603 | 2604 | #?(:clj 2605 | (defn forms-seq* 2606 | "Seq of Clojure/ClojureScript forms from rdr, a java.io.Reader. Optionally 2607 | accepts a filename argument which will be used in any emitted errors." 2608 | ([^Reader rdr] (forms-seq* rdr nil)) 2609 | ([^Reader rdr filename] 2610 | {:pre [(instance? Reader rdr)]} 2611 | (let [eof-sentinel (Object.) 2612 | opts (merge 2613 | {:eof eof-sentinel} 2614 | (if (and filename (= (util/ext filename) "cljc")) 2615 | {:read-cond :allow :features #{:cljs}})) 2616 | pbr (readers/indexing-push-back-reader 2617 | (PushbackReader. rdr) 1 filename) 2618 | data-readers tags/*cljs-data-readers* 2619 | forms-seq_ 2620 | (fn forms-seq_ [] 2621 | (lazy-seq 2622 | (let [form (binding [*ns* (create-ns *cljs-ns*) 2623 | reader/*data-readers* data-readers 2624 | reader/*alias-map* 2625 | (apply merge 2626 | ((juxt :requires :require-macros) 2627 | (get-namespace *cljs-ns*))) 2628 | reader/resolve-symbol resolve-symbol] 2629 | (reader/read opts pbr))] 2630 | (if (identical? form eof-sentinel) 2631 | (.close rdr) 2632 | (cons form (forms-seq_))))))] 2633 | (forms-seq_))))) 2634 | 2635 | #?(:clj 2636 | (defn forms-seq 2637 | "DEPRECATED: Seq of Clojure/ClojureScript forms from [f], which can be anything 2638 | for which `clojure.java.io/reader` can produce a `java.io.Reader`. Optionally 2639 | accepts a [filename] argument, which the reader will use in any emitted errors." 2640 | ([f] (forms-seq f (source-path f))) 2641 | ([f filename] (forms-seq f filename false)) 2642 | ([f filename return-reader?] 2643 | (let [rdr (io/reader f) 2644 | pbr (readers/indexing-push-back-reader 2645 | (PushbackReader. rdr) 1 filename) 2646 | data-readers tags/*cljs-data-readers* 2647 | forms-seq* 2648 | (fn forms-seq* [] 2649 | (lazy-seq 2650 | (let [eof-sentinel (Object.) 2651 | form (binding [*ns* (create-ns *cljs-ns*) 2652 | reader/*data-readers* data-readers 2653 | reader/*alias-map* 2654 | (apply merge 2655 | ((juxt :requires :require-macros) 2656 | (get-namespace *cljs-ns*)))] 2657 | (reader/read pbr nil eof-sentinel))] 2658 | (if (identical? form eof-sentinel) 2659 | (.close rdr) 2660 | (cons form (forms-seq*))))))] 2661 | (if (true? return-reader?) 2662 | [(forms-seq*) rdr] 2663 | (forms-seq*)))))) 2664 | 2665 | #?(:clj 2666 | (defn parse-ns 2667 | "Helper for parsing only the essential namespace information from a 2668 | ClojureScript source file and returning a cljs.closure/IJavaScript compatible 2669 | map _not_ a namespace AST node. 2670 | 2671 | By default does not load macros or perform any analysis of dependencies. If 2672 | opts parameter provided :analyze-deps and :load-macros keys their values will 2673 | be used for *analyze-deps* and *load-macros* bindings respectively. This 2674 | function does _not_ side-effect the ambient compilation environment unless 2675 | requested via opts where :restore is false." 2676 | ([src] (parse-ns src nil nil)) 2677 | ([src opts] (parse-ns src nil opts)) 2678 | ([src dest opts] 2679 | (ensure 2680 | (let [src (if (symbol? src) 2681 | (util/ns->source src) 2682 | src) 2683 | compiler-env @env/*compiler* 2684 | [ijs compiler-env'] 2685 | (binding [env/*compiler* (atom compiler-env) 2686 | *cljs-ns* 'cljs.user 2687 | *cljs-file* src 2688 | *macro-infer* 2689 | (or (when (contains? opts :macro-infer) 2690 | (:macro-infer opts)) 2691 | false) 2692 | *analyze-deps* 2693 | (or (when (contains? opts :analyze-deps) 2694 | (:analyze-deps opts)) 2695 | false) 2696 | *load-macros* 2697 | (or (when (contains? opts :load-macros) 2698 | (:load-macros opts)) 2699 | false)] 2700 | (let [rdr (when-not (sequential? src) (io/reader src))] 2701 | (try 2702 | (loop [forms (if rdr 2703 | (forms-seq* rdr (source-path src)) 2704 | src)] 2705 | (if (seq forms) 2706 | (let [env (empty-env) 2707 | ast (no-warn (analyze env (first forms) nil opts))] 2708 | (if (= :ns (:op ast)) 2709 | (let [ns-name (:name ast) 2710 | ns-name (if (and (= 'cljs.core ns-name) 2711 | (= "cljc" (util/ext src))) 2712 | 'cljs.core$macros 2713 | ns-name) 2714 | deps (merge (:uses ast) (:requires ast))] 2715 | [(merge 2716 | {:ns (or ns-name 'cljs.user) 2717 | :provides [ns-name] 2718 | :requires (if (= 'cljs.core ns-name) 2719 | (set (vals deps)) 2720 | (cond-> (conj (set (vals deps)) 'cljs.core) 2721 | (get-in compiler-env [:options :emit-constants]) 2722 | (conj 'constants-table))) 2723 | :file dest 2724 | :source-file (when rdr src) 2725 | :source-forms (when-not rdr src) 2726 | :ast ast 2727 | :macros-ns (or (:macros-ns opts) 2728 | (= 'cljs.core$macros ns-name))} 2729 | (when (and dest (.exists ^File dest)) 2730 | {:lines (with-open [reader (io/reader dest)] 2731 | (-> reader line-seq count))})) 2732 | @env/*compiler*]) 2733 | (recur (rest forms)))) 2734 | (throw (AssertionError. (str "No ns form found in " src))))) 2735 | (finally 2736 | (when rdr 2737 | (.close ^Reader rdr))))))] 2738 | (when (false? (:restore opts)) 2739 | (swap! env/*compiler* 2740 | (fn [old-state] 2741 | (-> old-state 2742 | (update-in [::namespaces] merge (get compiler-env' ::namespaces)) 2743 | (update-in [::constant-table] merge (get compiler-env' ::constant-table)))))) 2744 | ijs))))) 2745 | 2746 | #?(:clj 2747 | (defn cache-file 2748 | "Given a ClojureScript source file returns the read/write path to the analysis 2749 | cache file. Defaults to the read path which is usually also the write path." 2750 | ([src] (cache-file src "out")) 2751 | ([src output-dir] (cache-file src (parse-ns src) output-dir)) 2752 | ([src ns-info output-dir] (cache-file src (parse-ns src) output-dir :read)) 2753 | ([src ns-info output-dir mode] 2754 | {:pre [(map? ns-info)]} 2755 | (if-let [core-cache 2756 | (and (= mode :read) 2757 | (= (:ns ns-info) 'cljs.core) 2758 | (io/resource "cljs/core.cljs.cache.aot.edn"))] 2759 | core-cache 2760 | (let [target-file (util/to-target-file output-dir ns-info 2761 | (util/ext (:source-file ns-info)))] 2762 | (io/file (str target-file ".cache.edn"))))))) 2763 | 2764 | #?(:clj 2765 | (defn requires-analysis? 2766 | "Given a src, a resource, and output-dir, a compilation output directory 2767 | return true or false depending on whether src needs to be (re-)analyzed. 2768 | Can optionally pass cache, the analysis cache file." 2769 | ([src] (requires-analysis? src "out")) 2770 | ([src output-dir] 2771 | (let [cache (cache-file src output-dir)] 2772 | (requires-analysis? src cache output-dir))) 2773 | ([src cache output-dir] 2774 | (cond 2775 | (and (util/url? cache) 2776 | (.endsWith (.getPath ^URL cache) "cljs/core.cljs.cache.aot.edn")) 2777 | false 2778 | 2779 | (and (util/file? cache) 2780 | (not (.exists ^File cache))) 2781 | true 2782 | 2783 | :else 2784 | (let [out-src (util/to-target-file output-dir (parse-ns src))] 2785 | (if (not (.exists out-src)) 2786 | true 2787 | (if (> (util/last-modified src) (util/last-modified cache)) 2788 | true 2789 | (let [version' (util/compiled-by-version cache) 2790 | version (util/clojurescript-version)] 2791 | (and version (not= version version')))))))))) 2792 | 2793 | #?(:clj 2794 | (defn write-analysis-cache [ns cache-file] 2795 | (util/mkdirs cache-file) 2796 | (spit cache-file 2797 | (str ";; Analyzed by ClojureScript " (util/clojurescript-version) "\n" 2798 | (pr-str 2799 | (dissoc (get-in @env/*compiler* [::namespaces ns]) :macros)))))) 2800 | 2801 | #?(:clj 2802 | (defn analyze-file 2803 | "Given a java.io.File, java.net.URL or a string identifying a resource on the 2804 | classpath attempt to analyze it. 2805 | 2806 | This function side-effects the ambient compilation environment 2807 | `cljs.env/*compiler*` to aggregate analysis information. opts argument is 2808 | compiler options, if :cache-analysis true will cache analysis to 2809 | \":output-dir/some/ns/foo.cljs.cache.edn\". This function does not return a 2810 | meaningful value." 2811 | ([f] (analyze-file f nil)) 2812 | ([f opts] 2813 | (binding [*file-defs* (atom #{})] 2814 | (let [output-dir (util/output-directory opts) 2815 | res (cond 2816 | (instance? File f) f 2817 | (instance? URL f) f 2818 | (re-find #"^file://" f) (URL. f) 2819 | :else (io/resource f))] 2820 | (assert res (str "Can't find " f " in classpath")) 2821 | (ensure 2822 | (let [ns-info (parse-ns res) 2823 | path (if (instance? File res) 2824 | (.getPath ^File res) 2825 | (.getPath ^URL res)) 2826 | cache (when (:cache-analysis opts) 2827 | (cache-file res ns-info output-dir))] 2828 | (when-not (get-in @env/*compiler* [::namespaces (:ns ns-info) :defs]) 2829 | (if (or (not cache) (requires-analysis? res output-dir)) 2830 | (binding [*cljs-ns* 'cljs.user 2831 | *cljs-file* path 2832 | reader/*alias-map* (or reader/*alias-map* {})] 2833 | (when (or *verbose* (:verbose opts)) 2834 | (util/debug-prn "Analyzing" (str res))) 2835 | (let [env (assoc (empty-env) :build-options opts) 2836 | ns (with-open [rdr (io/reader res)] 2837 | (loop [ns nil forms (seq (forms-seq* rdr (util/path res)))] 2838 | (if forms 2839 | (let [form (first forms) 2840 | env (assoc env :ns (get-namespace *cljs-ns*)) 2841 | ast (analyze env form nil opts)] 2842 | (if (= (:op ast) :ns) 2843 | (recur (:name ast) (next forms)) 2844 | (recur ns (next forms)))) 2845 | ns)))] 2846 | (when (and cache (true? (:cache-analysis opts))) 2847 | (write-analysis-cache ns cache)))) 2848 | ;; we want want to keep dependency analysis information 2849 | ;; don't revert the environment - David 2850 | (let [{:keys [ns]} 2851 | (parse-ns res 2852 | (merge opts 2853 | {:restore false 2854 | :analyze-deps true 2855 | :load-macros true}))] 2856 | (when (or *verbose* (:verbose opts)) 2857 | (util/debug-prn "Reading analysis cache for" (str res))) 2858 | (swap! env/*compiler* 2859 | (fn [cenv] 2860 | (let [cached-ns (edn/read-string (slurp cache))] 2861 | (doseq [x (get-in cached-ns [::constants :order])] 2862 | (register-constant! x)) 2863 | (-> cenv 2864 | (assoc-in [::namespaces ns] cached-ns))))))))))))))) 2865 | --------------------------------------------------------------------------------