├── .gitignore ├── INSTALL.md ├── README.md ├── TODO.md ├── addon-info.json ├── autoload └── xolox │ └── luainspect.vim ├── doc └── luainspect.txt ├── example.lua ├── misc └── luainspect │ ├── luainspect │ ├── CHANGES │ ├── COPYRIGHT │ ├── README │ └── luainspect │ │ ├── ast.lua │ │ ├── command.lua │ │ ├── compat_env.lua │ │ ├── delimited.lua │ │ ├── dump.lua │ │ ├── globals.lua │ │ ├── html.lua │ │ ├── init.lua │ │ ├── scite.lua │ │ ├── signatures.lua │ │ ├── typecheck.lua │ │ └── types.lua │ ├── luainspect4vim.lua │ └── metalualib │ ├── LICENSE │ ├── README.TXT │ ├── gg.lua │ ├── lexer.lua │ ├── metalua │ ├── base.lua │ ├── runtime.lua │ ├── string2.lua │ └── table2.lua │ ├── mlp_expr.lua │ ├── mlp_ext.lua │ ├── mlp_lexer.lua │ ├── mlp_meta.lua │ ├── mlp_misc.lua │ ├── mlp_stat.lua │ └── mlp_table.lua └── plugin └── luainspect.vim /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | *Please note that the vim-lua-inspect plug-in requires my vim-misc plug-in which is separately distributed.* 2 | 3 | Unzip the most recent ZIP archives of the [vim-lua-inspect] [download-lua-inspect] and [vim-misc] [download-misc] plug-ins inside your Vim profile directory (usually this is `~/.vim` on UNIX and `%USERPROFILE%\vimfiles` on Windows), restart Vim and execute the command `:helptags ~/.vim/doc` (use `:helptags ~\vimfiles\doc` instead on Windows). 4 | 5 | If you prefer you can also use [Pathogen] [pathogen], [Vundle] [vundle] or a similar tool to install & update the [vim-lua-inspect] [github-lua-inspect] and [vim-misc] [github-misc] plug-ins using a local clone of the git repository. 6 | 7 | 8 | [download-lua-inspect]: http://peterodding.com/code/vim/downloads/lua-inspect.zip 9 | [download-misc]: http://peterodding.com/code/vim/downloads/misc.zip 10 | [github-lua-inspect]: http://github.com/xolox/vim-lua-inspect 11 | [github-misc]: http://github.com/xolox/vim-misc 12 | [pathogen]: http://www.vim.org/scripts/script.php?script_id=2332 13 | [vundle]: https://github.com/gmarik/vundle 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Semantic highlighting for Lua in Vim 2 | 3 | The Vim plug-in `luainspect.vim` uses the [LuaInspect] [lua-inspect] tool to (automatically) perform semantic highlighting of variables in Lua source code. It was inspired by [lua2-mode] [lua2-mode] (for [Emacs] [emacs]) and the [SciTE] [scite] plug-in included with LuaInspect. In addition to the semantic highlighting the following features are currently supported: 4 | 5 | * Press `` with the text cursor on a variable and the plug-in will prompt you to rename the variable. 6 | 7 | * Press `gd` (in normal mode) with the text cursor on a variable and you'll jump to its declaration / first occurrence. 8 | 9 | * When you hover over a variable with the mouse cursor in graphical Vim, information about the variable is displayed in a tooltip. 10 | 11 | * If the text cursor is on a variable while the highlighting is refreshed then all occurrences of the variable will be marked in the style of [Vim's cursorline option] [cursorline]. 12 | 13 | * When luainspect reports a wrong argument count for a function call the text will be highlighted with a green underline. When you hover over the highlighted text a tooltip shows the associated warning message. 14 | 15 | * When LuaInspect reports warnings about unused variables, wrong argument counts, etc. they are shown in a [location list window] [location-list]. 16 | 17 | * When a syntax error is found (during highlighting or using the rename functionality) the lines where the error is reported will be marked like a spelling error. 18 | 19 | ![Screenshot of semantic highlighting](http://peterodding.com/code/vim/luainspect/screenshot.png) 20 | 21 | ## Installation 22 | 23 | *Please note that the vim-lua-inspect plug-in requires my vim-misc plug-in which is separately distributed.* 24 | 25 | Unzip the most recent ZIP archives of the [vim-lua-inspect] [download-lua-inspect] and [vim-misc] [download-misc] plug-ins inside your Vim profile directory (usually this is `~/.vim` on UNIX and `%USERPROFILE%\vimfiles` on Windows), restart Vim and execute the command `:helptags ~/.vim/doc` (use `:helptags ~\vimfiles\doc` instead on Windows). 26 | 27 | If you prefer you can also use [Pathogen] [pathogen], [Vundle] [vundle] or a similar tool to install & update the [vim-lua-inspect] [github-lua-inspect] and [vim-misc] [github-misc] plug-ins using a local clone of the git repository. 28 | 29 | Now try it out: Edit a Lua file and within a few seconds semantic highlighting should be enabled automatically! 30 | 31 | Note that on Windows a command prompt window pops up whenever LuaInspect is run as an external process. If this bothers you then you can install my [shell.vim] [vim-shell] plug-in which includes a [DLL] [dll] that works around this issue. Once you've installed both plug-ins it should work out of the box! 32 | 33 | ## Usage 34 | 35 | When you open any Lua file the semantic highlighting should be enabled automatically within a few seconds, so you don't have to configure anything if you're happy with the defaults. 36 | 37 | ## Commands 38 | 39 | ### The `:LuaInspect` command 40 | 41 | You don't need to use this command unless you've disabled automatic highlighting using `g:lua_inspect_events`. When you execute this command the plug-in runs the LuaInspect tool and then highlights all variables in the current buffer using one of the following highlighting groups: 42 | 43 | * luaInspectGlobalDefined 44 | * luaInspectGlobalUndefined 45 | * luaInspectLocalUnused 46 | * luaInspectLocalMutated 47 | * luaInspectUpValue 48 | * luaInspectParam 49 | * luaInspectLocal 50 | * luaInspectFieldDefined 51 | * luaInspectFieldUndefined 52 | * luaInspectSelectedVariable 53 | * luaInspectWrongArgCount 54 | * luaInspectSyntaxError 55 | 56 | If you don't like one or more of the default styles the Vim documentation [describes how to change them] [hi-default]. If you want to disable the semantic highlighting in a specific Vim buffer execute `:LuaInspect!` in that buffer. When you want to re-enable the highlighting execute `:LuaInspect` again, but now without the [bang (!)] [bang]. 57 | 58 | ### The `:LuaInspectToggle` command 59 | 60 | By default the semantic highlighting and the warning messages in the location list window are automatically applied to Lua buffers and updated every once in a while, but this can be disabled by setting `g:lua_inspect_events` to an empty string in your [vimrc script] [vimrc]. If the plug-in is not automatically enabled then it may be useful to enable/disable it using a key mapping. That's what the `:LuaInspectToggle` command is for. You still have to define your key mapping of choice in your [vimrc script] [vimrc] though. For example: 61 | 62 | " Don't enable the lua-inspect plug-in automatically in Lua buffers. 63 | let g:lua_inspect_events = '' 64 | 65 | " Enable/disable the lua-inspect plug-in manually using . 66 | imap :LuaInspectToggle 67 | nmap :LuaInspectToggle 68 | 69 | ### The `:LuaInspectRename` command 70 | 71 | This command renames the variable under the cursor. It's used to define the `` mapping and can be used if you don't like the default mappings and want to define your own. 72 | 73 | ### The `:LuaInspectGoTo` command 74 | 75 | This command jumps to the definition of the variable under the cursor. It's used to define the `gd` mapping and can be used if you don't like the default mappings and want to define your own. 76 | 77 | ## Options 78 | 79 | ### The `g:loaded_luainspect` option 80 | 81 | This variable isn't really an option but if you want to avoid loading the `luainspect.vim` plug-in you can set this variable to any value in your [vimrc script] [vimrc]: 82 | 83 | :let g:loaded_luainspect = 1 84 | 85 | 86 | ### The `g:lua_inspect_mappings` option 87 | 88 | If this is set to true (1, the default value) then the `` and `gd` mappings are defined in Lua buffers (as buffer local mappings). You can set it to false (0) to disable the default mappings (so you can define your own). 89 | 90 | ### The `g:lua_inspect_warnings` option 91 | 92 | When LuaInspect reports warnings about unused variables, wrong argument counts, etc. they are automatically shown in a [location list window] [location-list]. If you don't like this add the following to your [vimrc script] [vimrc]: 93 | 94 | :let g:lua_inspect_warnings = 0 95 | 96 | ### The `g:lua_inspect_events` option 97 | 98 | By default semantic highlighting is automatically enabled after a short timeout and when you save a buffer. If you want to disable automatic highlighting altogether add the following to your [vimrc script] [vimrc]: 99 | 100 | :let g:lua_inspect_events = '' 101 | 102 | You can also add events, for example if you also want to run `:LuaInspect` the moment you edit a Lua file then try this: 103 | 104 | :let g:lua_inspect_events = 'CursorHold,CursorHoldI,BufReadPost,BufWritePost' 105 | 106 | Note that this only works when the plug-in is loaded (or reloaded) *after* setting the `g:lua_inspect_events` option. 107 | 108 | ### The `g:lua_inspect_internal` option 109 | 110 | The plug-in uses the Lua interface for Vim when available so that it doesn't have to run LuaInspect as an external program (which can slow things down). If you insist on running LuaInspect as an external program you can set this variable to false (0) in your [vimrc script] [vimrc]: 111 | 112 | :let g:lua_inspect_internal = 0 113 | 114 | ## Contact 115 | 116 | If you have questions, bug reports, suggestions, etc. the author can be contacted at . The latest version is available at and . If you like this plug-in please vote for it on [Vim Online] [vim-online]. 117 | 118 | ## License 119 | 120 | This software is licensed under the [MIT license] [mit]. 121 | © 2014 Peter Odding <>. 122 | 123 | The source code repository and distributions contain bundled copies of 124 | LuaInspect and Metalua, please refer to their licenses (also included). 125 | 126 | [bang]: http://vimdoc.sourceforge.net/htmldoc/map.html#:command-bang 127 | [cursorline]: http://vimdoc.sourceforge.net/htmldoc/options.html#%27cursorline%27 128 | [dll]: http://en.wikipedia.org/wiki/Dynamic-link_library 129 | [download-lua-inspect]: http://peterodding.com/code/vim/downloads/lua-inspect.zip 130 | [download-misc]: http://peterodding.com/code/vim/downloads/misc.zip 131 | [emacs]: http://www.gnu.org/software/emacs/ 132 | [github-lua-inspect]: http://github.com/xolox/vim-lua-inspect 133 | [github-misc]: http://github.com/xolox/vim-misc 134 | [hi-default]: http://vimdoc.sourceforge.net/htmldoc/syntax.html#:hi-default 135 | [location-list]: http://vimdoc.sourceforge.net/htmldoc/quickfix.html#location-list 136 | [lua-inspect]: http://lua-users.org/wiki/LuaInspect 137 | [lua2-mode]: http://www.enyo.de/fw/software/lua-emacs/lua2-mode.html 138 | [mit]: http://en.wikipedia.org/wiki/MIT_License 139 | [pathogen]: http://www.vim.org/scripts/script.php?script_id=2332 140 | [scite]: http://www.scintilla.org/SciTE.html 141 | [vim-online]: http://www.vim.org/scripts/script.php?script_id=3169 142 | [vim-shell]: http://peterodding.com/code/vim/shell/ 143 | [vimrc]: http://vimdoc.sourceforge.net/htmldoc/starting.html#vimrc 144 | [vundle]: https://github.com/gmarik/vundle 145 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # The to-do list 2 | 3 | * OMNI completion for in scope variables (including display of library function signatures). This will probably get complicated because most times when you want completion you'll already have typed half a statement, and LuaInspect will find syntax errors when trying to parse the source text. `scite.lua` can just use the last known valid AST but `luainspect4vim.lua` cannot keep this around when executed as an external process… 4 | * Document g:lua_inspect_path option. 5 | * Check whether "core/SciTE: jump to definition now supports functions in different files." is interesting. 6 | * Bug: The plug-in sometimes warns `Invalid output from luainspect4vim.lua: 'This is an unknown table field.'`. Mixup between tool tip / highlight response parsing?! 7 | 8 | # Known issues 9 | 10 | * Dynamic highlighting using Vim's `matchadd()` function performs **very** poorly in large buffers -- so poorly that Vim becomes pretty much unusable until the user disables dynamic highlighting using `:LuaInspect!` (e.g. try editing `luainspect/scite.lua` using the Vim plug-in). I've since switched to highlighting only function names (instead of multiline call expressions) but that hasn't improved performance as much as I'd hoped... 11 | -------------------------------------------------------------------------------- /addon-info.json: -------------------------------------------------------------------------------- 1 | {"vim_script_nr": 3169, "dependencies": {"vim-misc": {}}, "homepage": "http://peterodding.com/code/vim/lua-inspect", "name": "vim-lua-inspect"} -------------------------------------------------------------------------------- /autoload/xolox/luainspect.vim: -------------------------------------------------------------------------------- 1 | " Vim script. 2 | " Author: Peter Odding 3 | " Last Change: June 17, 2014 4 | " URL: http://peterodding.com/code/vim/lua-inspect/ 5 | 6 | let g:xolox#luainspect#version = '0.5.2' 7 | 8 | function! xolox#luainspect#toggle_cmd() " {{{1 9 | if !(exists('b:luainspect_disabled') && b:luainspect_disabled) 10 | " Enabled -> disabled. 11 | call xolox#luainspect#highlight_cmd(1) 12 | else 13 | " Disabled -> enabled. 14 | call xolox#luainspect#highlight_cmd(0) 15 | endif 16 | endfunction 17 | 18 | function! xolox#luainspect#auto_enable() " {{{1 19 | if !&diff && !exists('b:luainspect_disabled') 20 | " Disable easytags.vim because it doesn't play nice with luainspect.vim! 21 | let b:easytags_nohl = 1 22 | " Define buffer local mappings for rename / goto definition features. 23 | if g:lua_inspect_mappings 24 | inoremap :LuaInspectRename 25 | nnoremap :LuaInspectRename 26 | nnoremap gd :LuaInspectGoTo 27 | endif 28 | " Enable balloon evaluation / dynamic tool tips. 29 | if has('balloon_eval') 30 | setlocal ballooneval balloonexpr=LuaInspectToolTip() 31 | endif 32 | " Install automatic commands to update the highlighting. 33 | for event in split(g:lua_inspect_events, ',') 34 | execute 'autocmd!' event ' LuaInspect' 35 | endfor 36 | endif 37 | endfunction 38 | 39 | function! xolox#luainspect#highlight_cmd(disable) " {{{1 40 | if a:disable 41 | call s:clear_previous_matches() 42 | unlet! b:luainspect_input 43 | unlet! b:luainspect_output 44 | unlet! b:luainspect_warnings 45 | let b:luainspect_disabled = 1 46 | else 47 | unlet! b:luainspect_disabled 48 | call xolox#luainspect#make_request('highlight') 49 | endif 50 | endfunction 51 | 52 | function! xolox#luainspect#make_request(action) " {{{1 53 | let starttime = xolox#misc#timer#start() 54 | let bufnr = a:action != 'tooltip' ? bufnr('%') : v:beval_bufnr 55 | let bufname = bufname(bufnr) 56 | if bufname != '' 57 | let bufname = fnamemodify(bufname, ':p') 58 | endif 59 | if a:action == 'tooltip' 60 | let lines = getbufline(v:beval_bufnr, 1, "$") 61 | call insert(lines, v:beval_col) 62 | call insert(lines, v:beval_lnum) 63 | else 64 | let lines = getline(1, "$") 65 | call insert(lines, col('.')) 66 | call insert(lines, line('.')) 67 | endif 68 | call insert(lines, bufname) 69 | call insert(lines, a:action) 70 | call s:parse_text(join(lines, "\n"), s:prepare_search_path()) 71 | if !empty(b:luainspect_output) 72 | let response = b:luainspect_output[0] 73 | if bufname == '' 74 | let friendlyname = 'buffer #' . bufnr 75 | else 76 | let friendlyname = fnamemodify(bufname, ':~') 77 | endif 78 | if response == 'syntax_error' && len(b:luainspect_output) >= 4 79 | " Never perform syntax error highlighting in non-Lua buffers! 80 | let linenum = b:luainspect_output[1] + 0 81 | let colnum = b:luainspect_output[2] + 0 82 | let linenum2 = b:luainspect_output[3] + 0 83 | let b:luainspect_syntax_error = b:luainspect_output[4] 84 | if a:action != 'tooltip' || v:beval_bufnr == bufnr('%') 85 | let error_cmd = 'syntax match luaInspectSyntaxError /\%%>%il\%%<%il.*/ containedin=ALLBUT,lua*Comment*' 86 | execute printf(error_cmd, linenum - 1, (linenum2 ? linenum2 : line('$')) + 1) 87 | endif 88 | call xolox#misc#timer#stop("luainspect.vim %s: Found a syntax error in %s in %s.", g:xolox#luainspect#version, friendlyname, starttime) 89 | " But always let the user know that a syntax error exists. 90 | call xolox#misc#msg#warn("luainspect.vim %s: Syntax error around line %i in %s: %s", g:xolox#luainspect#version, linenum, friendlyname, b:luainspect_syntax_error) 91 | return 92 | endif 93 | unlet! b:luainspect_syntax_error 94 | if response == 'highlight' 95 | call s:define_default_styles() 96 | call s:clear_previous_matches() 97 | call s:highlight_variables() 98 | call xolox#misc#timer#stop("luainspect.vim %s: Highlighted variables in %s in %s.", g:xolox#luainspect#version, friendlyname, starttime) 99 | elseif response == 'go_to' 100 | if len(b:luainspect_output) < 3 101 | call xolox#misc#msg#warn("luainspect.vim %s: No variable under cursor!", g:xolox#luainspect#version) 102 | else 103 | let linenum = b:luainspect_output[1] + 0 104 | let colnum = b:luainspect_output[2] + 1 105 | call setpos('.', [0, linenum, colnum, 0]) 106 | call xolox#misc#timer#stop("luainspect.vim %s: Jumped to definition in %s in %s.", g:xolox#luainspect#version, friendlyname, starttime) 107 | if &verbose == 0 108 | " Clear previous "No variable under cursor!" message to avoid confusion. 109 | redraw | echo "" 110 | endif 111 | endif 112 | elseif response == 'tooltip' 113 | if len(b:luainspect_output) > 1 114 | call xolox#misc#timer#stop("luainspect.vim %s: Rendered tool tip for %s in %s.", g:xolox#luainspect#version, friendlyname, starttime) 115 | return join(b:luainspect_output[1:-1], "\n") 116 | endif 117 | elseif response == 'rename' 118 | if len(b:luainspect_output) > 1 119 | call xolox#misc#timer#stop("luainspect.vim %s: Prepared for rename in %s in %s.", g:xolox#luainspect#version, friendlyname, starttime) 120 | call s:rename_variable() 121 | else 122 | call xolox#misc#msg#warn("luainspect.vim %s: No variable under cursor!", g:xolox#luainspect#version) 123 | endif 124 | endif 125 | endif 126 | endfunction 127 | 128 | function! s:prepare_search_path() " {{{1 129 | let code = '' 130 | if !(has('lua') && g:lua_inspect_internal && exists('s:changed_path')) 131 | let root = xolox#misc#path#absolute(g:lua_inspect_path) 132 | let directories = [root] 133 | call add(directories, xolox#misc#path#merge(root, 'metalualib')) 134 | call add(directories, xolox#misc#path#merge(root, 'luainspect')) 135 | let template = "package.path = package.path .. ';%s/?.lua'" 136 | let lines = [] 137 | for directory in directories 138 | call add(lines, printf(template, escape(directory, '"\'''))) 139 | endfor 140 | let code = join(lines, '; ') 141 | if has('lua') && g:lua_inspect_internal 142 | execute 'lua' code 143 | let s:changed_path = 1 144 | endif 145 | endif 146 | return code 147 | endfunction 148 | 149 | function! s:parse_text(input, search_path) " {{{1 150 | if !(exists('b:luainspect_input') 151 | \ && exists('b:luainspect_output') 152 | \ && b:luainspect_input == a:input) 153 | if !(has('lua') && g:lua_inspect_internal) 154 | let template = 'lua -e "%s; require ''luainspect4vim'' (io.read ''*a'')"' 155 | let command = printf(template, a:search_path) 156 | call xolox#misc#msg#debug("luainspect.vim %s: Executing LuaInspect as external process using command: %s", g:xolox#luainspect#version, command) 157 | try 158 | let b:luainspect_output = xolox#misc#os#exec({'command': command . ' 2>&1', 'stdin': a:input})['stdout'] 159 | catch 160 | let msg = "luainspect.vim %s: Failed to execute LuaInspect as external process! Use ':verbose LuaInspect' to see the command line of the external process." 161 | throw printf(msg, g:xolox#luainspect#version) 162 | endtry 163 | else 164 | redir => output 165 | silent lua require 'luainspect4vim' (vim.eval 'a:input') 166 | redir END 167 | let b:luainspect_output = split(output, "\n") 168 | endif 169 | " Remember the text that was just parsed. 170 | let b:luainspect_input = a:input 171 | endif 172 | endfunction 173 | 174 | function! s:define_default_styles() " {{{1 175 | " Always define the default highlighting styles 176 | " (copied from /luainspect/scite.lua for consistency). 177 | for [group, styles] in items(s:groups) 178 | let group = 'luaInspect' . group 179 | if type(styles) == type('') 180 | let defgroup = styles 181 | else 182 | let defgroup = 'luaInspectDefault' . group 183 | let style = &bg == 'light' ? styles[0] : styles[1] 184 | execute 'highlight' defgroup style 185 | endif 186 | " Don't link the actual highlighting styles to the defaults if the user 187 | " has already defined or linked the highlighting group. This enables color 188 | " schemes and vimrc scripts to override the styles (see :help :hi-default). 189 | execute 'highlight def link' group defgroup 190 | unlet styles " to avoid E706. 191 | endfor 192 | endfunction 193 | 194 | function! s:clear_previous_matches() " {{{1 195 | " Clear existing highlighting. 196 | call clearmatches() 197 | for group in keys(s:groups) 198 | let group = 'luaInspect' . group 199 | if hlexists(group) 200 | execute 'syntax clear' group 201 | endif 202 | endfor 203 | endfunction 204 | 205 | function! s:highlight_variables() " {{{1 206 | call clearmatches() 207 | let num_warnings = b:luainspect_output[1] + 0 208 | call s:update_warnings(num_warnings > 0 ? b:luainspect_output[2 : num_warnings+1] : []) 209 | let other_output = b:luainspect_output[num_warnings+2 : -1] 210 | for line in other_output 211 | if s:check_output(line, '^\w\+\(\s\+\d\+\)\{4}$') 212 | let [group, l1, c1, l2, c2] = split(line) 213 | " Convert strings to numbers. 214 | let l1 += 0 215 | let l2 += 0 216 | " These adjustments were found by trial and error :-| 217 | let c1 += 0 218 | let c2 += 3 219 | if group == 'luaInspectWrongArgCount' 220 | call matchadd(group, s:highlight_position(l1, c1, l2, c2, 0)) 221 | elseif group == 'luaInspectSelectedVariable' 222 | call matchadd(group, s:highlight_position(l1, c1, l2, c2, 1), 20) 223 | else 224 | let pattern = s:highlight_position(l1, c1, l2, c2, 1) 225 | execute 'syntax match' group '/' . pattern . '/' 226 | endif 227 | endif 228 | endfor 229 | endfunction 230 | 231 | function! s:update_warnings(warnings) " {{{1 232 | if !g:lua_inspect_warnings 233 | return 234 | endif 235 | let list = [] 236 | for line in a:warnings 237 | if s:check_output(line, '^line\s\+\d\+\s\+column\s\+\d\+\s\+-\s\+\S') 238 | let fields = split(line) 239 | let linenum = fields[1] + 0 240 | let colnum = fields[3] + 0 241 | let message = join(fields[5:-1]) 242 | call add(list, { 'bufnr': bufnr('%'), 'lnum': linenum, 'col': colnum, 'text': message }) 243 | endif 244 | endfor 245 | call setloclist(winnr(), list) 246 | let b:luainspect_warnings = list 247 | if !empty(list) 248 | lopen 249 | if winheight(winnr()) > 4 250 | resize 4 251 | endif 252 | let warnings = len(list) > 1 ? 'warnings' : 'warning' 253 | let w:quickfix_title = printf('%i %s reported by LuaInspect', len(list), warnings) 254 | wincmd p 255 | else 256 | lclose 257 | endif 258 | endfunction 259 | 260 | function! s:rename_variable() " {{{1 261 | " Highlight occurrences of variable before rename. 262 | let highlights = [] 263 | for line in b:luainspect_output[1:-1] 264 | if s:check_output(line, '^\d\+\(\s\+\d\+\)\{2}$') 265 | let [l1, c1, c2] = split(line) 266 | " Convert string to number. 267 | let l1 += 0 268 | " These adjustments were found by trial and error :-| 269 | let c1 += 0 270 | let c2 += 3 271 | let pattern = s:highlight_position(l1, c1, l1, c2, 1) 272 | call add(highlights, matchadd('IncSearch', pattern)) 273 | endif 274 | endfor 275 | redraw 276 | " Prompt for new name. 277 | let oldname = expand('') 278 | let prompt = "luainspect.vim %s: Please enter the new name for %s: " 279 | let newname = input(printf(prompt, g:xolox#luainspect#version, oldname), oldname) 280 | " Clear highlighting of occurrences. 281 | call map(highlights, 'matchdelete(v:val)') 282 | " Perform rename? 283 | if newname != '' && newname !=# oldname 284 | let num_renamed = 0 285 | for fields in reverse(b:luainspect_output[1:-1]) 286 | let [linenum, firstcol, lastcol] = split(fields) 287 | " Convert string to number. 288 | let linenum += 0 289 | " These adjustments were found by trial and error :-| 290 | let firstcol -= 1 291 | let lastcol += 1 292 | let line = getline(linenum) 293 | let prefix = firstcol > 0 ? line[0 : firstcol] : '' 294 | let suffix = lastcol < len(line) ? line[lastcol : -1] : '' 295 | call setline(linenum, prefix . newname . suffix) 296 | let num_renamed += 1 297 | endfor 298 | let msg = "luainspect.vim %s: Renamed %i occurrences of %s to %s" 299 | call xolox#misc#msg#info(msg, g:xolox#luainspect#version, num_renamed, oldname, newname) 300 | endif 301 | endfunction 302 | 303 | function! s:check_output(line, pattern) " {{{1 304 | if match(a:line, a:pattern) >= 0 305 | return 1 306 | else 307 | call xolox#misc#msg#warn("luainspect.vim %s: Invalid output from luainspect4vim.lua: '%s'", g:xolox#luainspect#version, strtrans(a:line)) 308 | return 0 309 | endif 310 | endfunction 311 | 312 | function! s:highlight_position(l1, c1, l2, c2, ident_only) " {{{1 313 | let l1 = a:l1 >= 1 ? (a:l1 - 1) : a:l1 314 | let p = '\%>' . l1 . 'l\%>' . a:c1 . 'c' 315 | let p .= a:ident_only ? '\<\w\+\>' : '\_.\+' 316 | return p . '\%<' . (a:l2 + 1) . 'l\%<' . a:c2 . 'c' 317 | endfunction 318 | 319 | " Highlighting groups and their default light/dark styles. {{{1 320 | 321 | let s:groups = {} 322 | let s:groups['GlobalDefined'] = ['guifg=#600000', 'guifg=#ffc080'] 323 | let s:groups['GlobalUndefined'] = 'ErrorMsg' 324 | let s:groups['LocalUnused'] = ['guifg=#ffffff guibg=#000080', 'guifg=#ffffff guibg=#000080'] 325 | let s:groups['LocalMutated'] = ['gui=italic guifg=#000080', 'gui=italic guifg=#c0c0ff'] 326 | let s:groups['UpValue'] = ['guifg=#0000ff', 'guifg=#e8e8ff'] 327 | let s:groups['Param'] = ['guifg=#000040', 'guifg=#8080ff'] 328 | let s:groups['Local'] = ['guifg=#000040', 'guifg=#c0c0ff'] 329 | let s:groups['FieldDefined'] = ['guifg=#600000', 'guifg=#ffc080'] 330 | let s:groups['FieldUndefined'] = ['guifg=#c00000', 'guifg=#ff0000'] 331 | let s:groups['SelectedVariable'] = 'CursorLine' 332 | let s:groups['SyntaxError'] = 'SpellBad' 333 | let s:groups['WrongArgCount'] = 'SpellLocal' 334 | -------------------------------------------------------------------------------- /doc/luainspect.txt: -------------------------------------------------------------------------------- 1 | *luainspect.txt* Semantic highlighting for Lua in Vim 2 | 3 | =============================================================================== 4 | Contents ~ 5 | 6 | 1. Introduction |luainspect-introduction| 7 | 2. Installation |luainspect-installation| 8 | 3. Usage |luainspect-usage| 9 | 4. Commands |luainspect-commands| 10 | 1. The |:LuaInspect| command 11 | 2. The |:LuaInspectToggle| command 12 | 3. The |:LuaInspectRename| command 13 | 4. The |:LuaInspectGoTo| command 14 | 5. Options |luainspect-options| 15 | 1. The |g:loaded_luainspect| option 16 | 2. The |g:lua_inspect_mappings| option 17 | 3. The |g:lua_inspect_warnings| option 18 | 4. The |g:lua_inspect_events| option 19 | 5. The |g:lua_inspect_internal| option 20 | 6. Contact |luainspect-contact| 21 | 7. License |luainspect-license| 22 | 8. References |luainspect-references| 23 | 24 | =============================================================================== 25 | *luainspect-introduction* 26 | Introduction ~ 27 | 28 | The Vim plug-in 'luainspect.vim' uses the LuaInspect [1] tool to 29 | (automatically) perform semantic highlighting of variables in Lua source code. 30 | It was inspired by lua2-mode [2] (for Emacs [3]) and the SciTE [4] plug-in 31 | included with LuaInspect. In addition to the semantic highlighting the 32 | following features are currently supported: 33 | 34 | - Press '' with the text cursor on a variable and the plug-in will prompt 35 | you to rename the variable. 36 | 37 | - Press 'gd' (in normal mode) with the text cursor on a variable and you'll 38 | jump to its declaration / first occurrence. 39 | 40 | - When you hover over a variable with the mouse cursor in graphical Vim, 41 | information about the variable is displayed in a tooltip. 42 | 43 | - If the text cursor is on a variable while the highlighting is refreshed 44 | then all occurrences of the variable will be marked in the style of Vim's 45 | cursorline option (see |'cursorline'|). 46 | 47 | - When luainspect reports a wrong argument count for a function call the text 48 | will be highlighted with a green underline. When you hover over the 49 | highlighted text a tooltip shows the associated warning message. 50 | 51 | - When LuaInspect reports warnings about unused variables, wrong argument 52 | counts, etc. they are shown in a location list window (see |location- 53 | list|). 54 | 55 | - When a syntax error is found (during highlighting or using the rename 56 | functionality) the lines where the error is reported will be marked like a 57 | spelling error. 58 | 59 | Image: Screenshot of semantic highlighting (see reference [5]) 60 | 61 | =============================================================================== 62 | *luainspect-installation* 63 | Installation ~ 64 | 65 | _Please note that the vim-lua-inspect plug-in requires my vim-misc plug-in 66 | which is separately distributed._ 67 | 68 | Unzip the most recent ZIP archives of the vim-lua-inspect [6] and vim-misc [7] 69 | plug-ins inside your Vim profile directory (usually this is '~/.vim' on UNIX 70 | and '%USERPROFILE%\vimfiles' on Windows), restart Vim and execute the command 71 | ':helptags ~/.vim/doc' (use ':helptags ~\vimfiles\doc' instead on Windows). 72 | 73 | If you prefer you can also use Pathogen [8], Vundle [9] or a similar tool to 74 | install & update the vim-lua-inspect [10] and vim-misc [11] plug-ins using a 75 | local clone of the git repository. 76 | 77 | Now try it out: Edit a Lua file and within a few seconds semantic highlighting 78 | should be enabled automatically! 79 | 80 | Note that on Windows a command prompt window pops up whenever LuaInspect is run 81 | as an external process. If this bothers you then you can install my shell.vim 82 | [12] plug-in which includes a DLL [13] that works around this issue. Once 83 | you've installed both plug-ins it should work out of the box! 84 | 85 | =============================================================================== 86 | *luainspect-usage* 87 | Usage ~ 88 | 89 | When you open any Lua file the semantic highlighting should be enabled 90 | automatically within a few seconds, so you don't have to configure anything if 91 | you're happy with the defaults. 92 | 93 | =============================================================================== 94 | *luainspect-commands* 95 | Commands ~ 96 | 97 | ------------------------------------------------------------------------------- 98 | The *:LuaInspect* command 99 | 100 | You don't need to use this command unless you've disabled automatic 101 | highlighting using |g:lua_inspect_events|. When you execute this command the 102 | plug-in runs the LuaInspect tool and then highlights all variables in the 103 | current buffer using one of the following highlighting groups: 104 | 105 | - luaInspectGlobalDefined 106 | - luaInspectGlobalUndefined 107 | - luaInspectLocalUnused 108 | - luaInspectLocalMutated 109 | - luaInspectUpValue 110 | - luaInspectParam 111 | - luaInspectLocal 112 | - luaInspectFieldDefined 113 | - luaInspectFieldUndefined 114 | - luaInspectSelectedVariable 115 | - luaInspectWrongArgCount 116 | - luaInspectSyntaxError 117 | 118 | If you don't like one or more of the default styles the Vim documentation 119 | describes how to change them (see |:hi-default|). If you want to disable the 120 | semantic highlighting in a specific Vim buffer execute ':LuaInspect!' in that 121 | buffer. When you want to re-enable the highlighting execute |:LuaInspect| 122 | again, but now without the bang (!) (see |:command-bang|). 123 | 124 | ------------------------------------------------------------------------------- 125 | The *:LuaInspectToggle* command 126 | 127 | By default the semantic highlighting and the warning messages in the location 128 | list window are automatically applied to Lua buffers and updated every once in 129 | a while, but this can be disabled by setting |g:lua_inspect_events| to an empty 130 | string in your |vimrc| script. If the plug-in is not automatically enabled then 131 | it may be useful to enable/disable it using a key mapping. That's what the 132 | |:LuaInspectToggle| command is for. You still have to define your key mapping 133 | of choice in your |vimrc| script though. For example: 134 | > 135 | " Don't enable the lua-inspect plug-in automatically in Lua buffers. 136 | let g:lua_inspect_events = '' 137 | 138 | " Enable/disable the lua-inspect plug-in manually using . 139 | imap :LuaInspectToggle 140 | nmap :LuaInspectToggle 141 | < 142 | ------------------------------------------------------------------------------- 143 | The *:LuaInspectRename* command 144 | 145 | This command renames the variable under the cursor. It's used to define the 146 | '' mapping and can be used if you don't like the default mappings and want 147 | to define your own. 148 | 149 | ------------------------------------------------------------------------------- 150 | The *:LuaInspectGoTo* command 151 | 152 | This command jumps to the definition of the variable under the cursor. It's 153 | used to define the 'gd' mapping and can be used if you don't like the default 154 | mappings and want to define your own. 155 | 156 | =============================================================================== 157 | *luainspect-options* 158 | Options ~ 159 | 160 | ------------------------------------------------------------------------------- 161 | The *g:loaded_luainspect* option 162 | 163 | This variable isn't really an option but if you want to avoid loading the 164 | 'luainspect.vim' plug-in you can set this variable to any value in your |vimrc| 165 | script: 166 | > 167 | :let g:loaded_luainspect = 1 168 | < 169 | ------------------------------------------------------------------------------- 170 | The *g:lua_inspect_mappings* option 171 | 172 | If this is set to true (1, the default value) then the '' and 'gd' mappings 173 | are defined in Lua buffers (as buffer local mappings). You can set it to false 174 | (0) to disable the default mappings (so you can define your own). 175 | 176 | ------------------------------------------------------------------------------- 177 | The *g:lua_inspect_warnings* option 178 | 179 | When LuaInspect reports warnings about unused variables, wrong argument counts, 180 | etc. they are automatically shown in a location list window (see |location- 181 | list|). If you don't like this add the following to your |vimrc| script: 182 | > 183 | :let g:lua_inspect_warnings = 0 184 | < 185 | ------------------------------------------------------------------------------- 186 | The *g:lua_inspect_events* option 187 | 188 | By default semantic highlighting is automatically enabled after a short timeout 189 | and when you save a buffer. If you want to disable automatic highlighting 190 | altogether add the following to your |vimrc| script: 191 | > 192 | :let g:lua_inspect_events = '' 193 | < 194 | You can also add events, for example if you also want to run |:LuaInspect| the 195 | moment you edit a Lua file then try this: 196 | > 197 | :let g:lua_inspect_events = 'CursorHold,CursorHoldI,BufReadPost,BufWritePost' 198 | < 199 | Note that this only works when the plug-in is loaded (or reloaded) _after_ 200 | setting the |g:lua_inspect_events| option. 201 | 202 | ------------------------------------------------------------------------------- 203 | The *g:lua_inspect_internal* option 204 | 205 | The plug-in uses the Lua interface for Vim when available so that it doesn't 206 | have to run LuaInspect as an external program (which can slow things down). If 207 | you insist on running LuaInspect as an external program you can set this 208 | variable to false (0) in your |vimrc| script: 209 | > 210 | :let g:lua_inspect_internal = 0 211 | < 212 | =============================================================================== 213 | *luainspect-contact* 214 | Contact ~ 215 | 216 | If you have questions, bug reports, suggestions, etc. the author can be 217 | contacted at peter@peterodding.com. The latest version is available at 218 | http://peterodding.com/code/vim/lua-inspect/ and http://github.com/xolox/vim- 219 | lua-inspect. If you like this plug-in please vote for it on Vim Online [14]. 220 | 221 | =============================================================================== 222 | *luainspect-license* 223 | License ~ 224 | 225 | This software is licensed under the MIT license [15]. Š 2014 Peter Odding 226 | . 227 | 228 | The source code repository and distributions contain bundled copies of 229 | LuaInspect and Metalua, please refer to their licenses (also included). 230 | 231 | =============================================================================== 232 | *luainspect-references* 233 | References ~ 234 | 235 | [1] http://lua-users.org/wiki/LuaInspect 236 | [2] http://www.enyo.de/fw/software/lua-emacs/lua2-mode.html 237 | [3] http://www.gnu.org/software/emacs/ 238 | [4] http://www.scintilla.org/SciTE.html 239 | [5] http://peterodding.com/code/vim/luainspect/screenshot.png 240 | [6] http://peterodding.com/code/vim/downloads/lua-inspect.zip 241 | [7] http://peterodding.com/code/vim/downloads/misc.zip 242 | [8] http://www.vim.org/scripts/script.php?script_id=2332 243 | [9] https://github.com/gmarik/vundle 244 | [10] http://github.com/xolox/vim-lua-inspect 245 | [11] http://github.com/xolox/vim-misc 246 | [12] http://peterodding.com/code/vim/shell/ 247 | [13] http://en.wikipedia.org/wiki/Dynamic-link_library 248 | [14] http://www.vim.org/scripts/script.php?script_id=3169 249 | [15] http://en.wikipedia.org/wiki/MIT_License 250 | 251 | vim: ft=help 252 | -------------------------------------------------------------------------------- /example.lua: -------------------------------------------------------------------------------- 1 | -- A local variable masking a global one: 2 | global_variable = '1st value' 3 | do 4 | local global_variable = 'NOT ACTUALLY A GLOBAL!' 5 | print(global_variable) 6 | end 7 | global_variable = '2nd value' 8 | 9 | -- Highlighting for different types of locals: 10 | local usedlocal = 'this is a local variable' 11 | local mutated = "this one's assigned multiple times" 12 | local unused = "and this one isn't referenced anywhere" 13 | mutated = 'this is the second value of [mutated]' 14 | print(usedlocal) 15 | print(undefined) 16 | 17 | io.open('') 18 | string.lower('') 19 | local tinsert = table.insert 20 | local test = { field = function(foo, bar, baz) print(baz) end } 21 | 22 | -- Highlighting for function arguments: 23 | local function example(usedparam, mutatedparam, unusedparam) 24 | mutatedparam = 42 25 | print(usedparam) 26 | end 27 | 28 | -- Argument count checking: 29 | 30 | example(1) 31 | 32 | for k, v in pairs(_G) do print(k) end 33 | 34 | example ( 35 | 1, 36 | 2, 37 | 3, 38 | 4) 39 | 40 | -- Syntax errors: 41 | -- example(..) 42 | -------------------------------------------------------------------------------- /misc/luainspect/luainspect/CHANGES: -------------------------------------------------------------------------------- 1 | Change Log. 2 | 3 | 20120127 4 | [*] core: cleanup error messages in inferred values. 5 | 6 | 20120126 7 | [+] Ignore locals named '_' in unused/masking variable reporting. 8 | 9 | 20111224 10 | [+] html/delimited: export type information (in same manner as SciTE) 11 | [*] html: improve line number CSS treatment. e.g. don't include in copy/paste 12 | [+] html: highlight range of lines of scope of selected variable (like in SciTE). 13 | [+] command: add 'luainspect' front-end script in top directory. 14 | [+] command: add options for output name and html library path 15 | 16 | 20100911 17 | [+] core: infer types of for loop variables. 18 | 19 | 20100827 20 | [+] core: infer sets involving functions with multiple returns. 21 | e.g. local a,b = (function() return 1,2 end)() 22 | [!] core:fix: do not infer table sets on LuaInspect types. 23 | 24 | 20100825 25 | [*] SciTE: simplify install (use default path) 26 | [!] core: fix: function params should infer to unknown values 27 | [!] core: fix: infer: unknown functions return unknown values 28 | 29 | 20100823 30 | [*] SciTE: change Ctrl-Alt-W to Ctrl-Alt-E 31 | [!] SciTE: fix bookmarking (Ctrl+F2) 32 | [+] SciTE: bundle copy of extman.lua 33 | 34 | 20100821 35 | [+!] core: return analysis enabled following fixes 36 | 37 | 20100820 38 | [!] SciTE: fix folding performance problem (though folding still disabled by default 39 | due to OnStyle recursion problem) 40 | 41 | 20100819 42 | [!] core: fix tokenlist when opcode operands reversed lexically 43 | [*] metalua/performance - avoid overriding builtin pairs/ipairs 44 | [*] SciTE: plugin now loaded as Lua extension script (not globally). 45 | 46 | 20100818 47 | [!] HTML: fix missing chars at end-of-file 48 | [!] Metalua: fix lexer line number count off-by-one error 49 | [!] SciTE: fix Unicode/UTF-8 encoding breaking formatting 50 | [!] core: fix performance problem with tinsertlist function 51 | [!] core/performance: cleanup invalidated_code function 52 | 53 | 20100817 54 | [!] core: fix keyword token recognition problems 55 | [!] core: skip inspection on require loops 56 | [+] core: infer function return values (temporarily disabled) 57 | [+] core: detect dead-code (temporarily disabled) 58 | [*] core: internal refactoring (ast.valueknown) 59 | 60 | 20100816 61 | core: make reporting optional 62 | metalua: patches to metalua lineinfo 63 | (was corrupting HTML output and SciTE highlighting) 64 | 65 | 20100814 66 | core: add basic type inferences (e.g. number+number -> number) 67 | 68 | 20100813 69 | core: inspect required modules too 70 | (e.g. enables use of imported function signatures) 71 | core/SciTE: add list all warnings command (SciTE: Ctrl+Alt+W lists, and F4 iterates them) 72 | 73 | 20100811 74 | SciTE: autocomplete functions arguments when cursor after '(' 75 | core: fix signatures for os/debug libraries 76 | core/SciTE: display function argument list or helpinfo for variables 77 | SciTE: Ctrl+Alt+I changed to Ctrl+Alt+B to avoid conflict with 78 | SciTE 2.20 incremental search 79 | 80 | 20100810 81 | SciTE: improved "inspect variable" command, supports browsing nested tables. 82 | SciTE: split luainspect.autocomplete property into two properties 83 | SciTE: add autocomplete function 84 | SciTE: autocomplete table fields. 85 | 86 | 20100809 87 | core/SciTE: add function argument count check 88 | core/SciTE: jump to definition now supports functions in different files. 89 | core/SciTE/HTML: improvements to displaying masking/masked lexicals. 90 | core/SciTE: add command to just to previous statement 91 | core/SciTE: preliminary variable autocomplete support 92 | (luainspect.autocomplete currently disabled by default) 93 | SciTE: add missing style.script_lua.local_param_mutate style. 94 | 95 | 20100807 96 | SciTE: Add luainspect.path.append/luainspect.cpath.append properties 97 | to append to package.path/cpath 98 | SciTE: Add custom searcher function to locate modules in same path as current buffer. 99 | SciTE: Added "force reinspect" command to force full reinspection of code. 100 | Note: this will also attempt to unload any modules loaded by previous inspection. 101 | SciTE: Improve luainspect.update.delay to delay inspection for given tick count 102 | following user typing. Also displays blue '+' marker when inspection has been delayed. 103 | 104 | 20100806 105 | SciTE: jump to uses, not jumps to exact position, not just line number 106 | SciTE: mark lines of invalidated code upon introducing code errors and display 107 | error message below invalidated code (not on exact line of error) 108 | SciTE: add styling delay option to improve performance (luainspect.update.delay) 109 | SciTE: preliminary auto-complete typing support (luainspect.autocomplete) 110 | (experimental and currently off by default) 111 | 112 | 20100805 113 | core: Major internal refactoring to simplify incremental compilation 114 | (lineinfo managed in tokenlist). Breaks API. 115 | core/SciTE/HTML: identifies local variables that mask other locals (same name): 116 | e.g. local x=1; local x=2 (strikethrough) 117 | core: added version number variable APIVERSION to luainspect.init. 118 | HTML: highlight keywords in selected block 119 | SciTE: the incremental compilation feature is now on by default. 120 | 121 | 20100803 122 | core:Evaluate special comments (prefixed by '!') to inject semantic information into analysis 123 | (similar to luaanalyze). 124 | core: Further work on incremental compilation feature. 125 | 126 | 20100802 127 | core: improve field value inferences 128 | SciTE: improve dark style clarity 129 | SciTE: make margin markers for variable scope and block mutually exclusive 130 | 131 | 20100731 132 | SciTE: allow styles in properties to be specified by name and more flexibly overridden. 133 | SciTE: add optional dark style 134 | SciTE/HTML: support mutate upvalues, cleanup styles 135 | SciTE: improve keyword highlighting (always highlight containing block) 136 | 137 | 20100730 138 | core: fix scoping of `for` statements (in globals.lua) 139 | core/SciTE: highlight keywords and show all keywords in selected statement. 140 | 141 | 20100729 142 | SciTE: options can now be set with SciTE properties. 143 | SciTE: refactor: select statement 144 | core/SciTE: more work on incremental compilation (luainspect.incremental.compilation) 145 | 146 | 20100728 147 | core/SciTE: add command to select statement or comment containing current cursor selection. 148 | core/SciTE: experimental incremental compilation option (ALLOW_INCREMENTAL_COMPILATION) 149 | core/SciTE: add special styling (background color) for tab whitespace 150 | 151 | 20100727 152 | SciTE: Fix limited styling range may skip styling (broke in 20100726) 153 | 154 | 20100726 155 | SciTE: apply default styles in script if not specified in properties file. 156 | SciTE: initial implementation of folding (but currently disabled due to SciTE problems) 157 | SciTE: improve OnStyle only over provided byte range 158 | Note: you may now remove LuaInspect styles from your properties file. 159 | 160 | 20100725 161 | SciTE: fix memory overflow when code contains buffer.notes. 162 | 163 | 20100724 164 | SciTE: list all uses of selected variable (currently locals only) 165 | SciTE: display errors about mismatched blocks or parens at both top and bottom of problem 166 | SciTE: support shebang line 167 | 168 | 20100723 169 | core/SciTE/HTML: Initial support for table fields 170 | core/SciTE: initial dynamic value determination 171 | core: fix recursive local scoping (`Localrec) in globals.lua 172 | SciTE: Mark all range of selected variable's scope in margin 173 | SciTE: New command to rename all occurrences of selected variable 174 | SciTE: Significant performance gain utilizing loadstring in addition 175 | to metalua libraries 176 | SciTE: Mark upvalues (lighter blue) 177 | SciTE: Fix handling multiple buffers. 178 | SciTE: display variable info on double click 179 | SciTE: display real-time annotations of all local variables, like a Mathcad worksheet 180 | (experimental feature via ANNOTATE_ALL_LOCALS) 181 | SciTE: jump (goto) definition of selected variable (currently locals only) 182 | ctagsdx.lua from the full SciteExtMan is optional (allows "goto mark" command 183 | to return to previous location following a "go to definition"). 184 | SciTE: add command to inspect table contents. 185 | Note: SciTE*.properties and luainspect.css have been updated; please update when upgrading 186 | 187 | 20100720 188 | core: support for detecting unused locals (white on blue) 189 | SciTE: display callinfo help on top-level standard library globals 190 | SciTE: display local parameters distinctly (dark blue) 191 | SciTE: display compiler errors as annotations 192 | SciTE: partial workaround for conflict with other lexers 193 | SciTE: option to recompile only when cursor line number changes to improve performance 194 | and reduce error reporting (set UPDATE_ALWAYS to true in scite.lua to enable this) 195 | SciTE: workaround for Metalua libraries sometimes not returning line number in error report 196 | Note: SciTE*.properties and luainspect.css have been updated; please update when upgrading 197 | 198 | 20100719 199 | core: Fixed "repeat" statement scope handling (globals.lua) 200 | SciTE: Improve performance (not recompile when code not changing) 201 | SciTE: Add "!" marker near compiler error. 202 | SciTE: Add hotspots on local variables 203 | 204 | 20100717-2 205 | SciTE: highlight all instances of selected identifier 206 | Now requires http://lua-users.org/wiki/SciteExtMan 207 | 208 | 20100717 209 | added initial SciTE text editor plugin 210 | 211 | 20100622 212 | initial version with HTML output 213 | -------------------------------------------------------------------------------- /misc/luainspect/luainspect/COPYRIGHT: -------------------------------------------------------------------------------- 1 | LuaInspect License 2 | 3 | Copyright (C) 2010 David Manura 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | =============================================================================== 24 | 25 | Uses Metalua libraries (see metalualib/LICENSE). 26 | Uses jquery (see COPYRIGHT-jquery) 27 | Uses ExtMan (see COPYRIGHT-extman) 28 | 29 | -------------------------------------------------------------------------------- /misc/luainspect/luainspect/README: -------------------------------------------------------------------------------- 1 | LuaInspect - LuaInspect is a tool that does Lua code analysis. 2 | It includes an extensive plugin for the SciTE [1] text editor, 3 | there is also a plugin for the VIM editor [2], and it includes 4 | an export to DHTML as well. 5 | 6 | == Project Page == 7 | 8 | For further details, see http://lua-users.org/wiki/LuaInspect . 9 | 10 | == Status == 11 | 12 | WARNING: Some of this code might not yet be stable or complete, 13 | particularly with regards to inferencing. It is usable for daily code editing 14 | but you may need to sometimes fix things yourself. Many additional 15 | features could be added too. 16 | 17 | == Features == 18 | 19 | * analysis: 20 | * identifies global (red) and local variables (blue), including locals that are 21 | function arguments (dark blue) and upvalues (light blue) 22 | * identifies unused local variables: e.g. `do local x=1 end` (white-on-blue) 23 | * identifies local variables masking other locals (same name): e.g. `local x=1; local x=2` 24 | (strikethrough and squiggle line) 25 | * identifies local variables that have non-constant binding (`local x = 1; x = 2`) (italic) 26 | * identifies unknown global variables (white-on-red) and table fields (red), inferred by 27 | static and dynamic evaluation. 28 | * infers values of variables (e.g. `local sum = math.pi + 2` is 5.14. 29 | and defined-ness of members of imported modules 30 | (`local mt = require "math"; math.sqrtt(2) -- undefined`) 31 | * infers signatures of functions (including local, global, and module functions) 32 | * checks number of function arguments against signatures 33 | * cross-references variables (locals and module fields) with their definitions and uses 34 | (pink highlight), identifies range of lines/scope where the local is defined 35 | and (SciTE only) supports jump-to-definition and jump-to-uses 36 | * identifies all keywords in selected block (underline) 37 | * evaluate special comments (prefixed by '!') to inject semantic information into analysis 38 | (similar to luaanalyze / lint). 39 | * basic type inferences (e.g. number + number -> number) 40 | * infer function return values (e.g. `function f(x) if x then return 1,2,3 else return 1,3,'z' end end` 41 | returns 1, number, unknown). 42 | * detect dead-code (e.g. `do return end dead()`) (SciTE only) (diagonal hatching) 43 | * refactoring: 44 | * command to rename all occurrences of selected variable (SciTE only) 45 | * browsing: 46 | * inspect members of selected table. 47 | * select statement or comment containing current cursor selection (SciTE only) 48 | * display real-time annotations of all local variables, like an Excel/Mathcad worksheet 49 | (experimental feature via ANNOTATE_ALL_LOCALS) (currently SciTE only) 50 | * auto-complete typing support (SciTE only) (experimental) 51 | * interfaces: SciTE plugin, VIM plugin, and HTML output. 52 | 53 | == Files in this directory == 54 | 55 | metalualib/* - Copy of Metalua libraries. 56 | Based on http://github.com/fab13n/metalua/tree/fcee97b8d0091ceb471902ee457dbccaab98234e 57 | with a few bug fixes (search for "PATCHED:LuaInspect" in the source). 58 | lib/* - LuaInspect libraries. 59 | htmllib/* - HTML resources under here. 60 | extman/* - SciTE extman. 61 | Recent version compatible with LuaInspect. 62 | 63 | == Command-line Usage (HTML output) == 64 | 65 | Example: 66 | 67 | $ ./luainspect -fhtml -lhtmllib examples.lua > examples.html 68 | 69 | (Alternately just run "lua test.lua". You should also do "lua luainspect" 70 | rather than "./luainspect" on Windows.) 71 | 72 | You will need to ensure that the JavaScript and CSS files in the 73 | path after the "-l" argument can be found relative to the HTML file; 74 | otherwise, the page will not display properly. 75 | 76 | == Command-line Usage (delimited CSV output) == 77 | 78 | Example: 79 | 80 | $ ./luainspect -fdelimited examples.lua > examples.csv 81 | 82 | == Installation in SciTE == 83 | 84 | First install SciTE . 85 | Version 2.12 and 2.20 work (older versions might not work). 86 | 87 | The simple way to install LuaInspect into SciTE is to just place the 88 | "luainspect" folder inside the same folder where your SciTE binary is 89 | installed and add the following line to one of your SciTE properties 90 | files (e.g. SciTEGlobal.properties or SciTEUser.properties -- consult 91 | the SciTE documentation for where these are located): 92 | 93 | ext.lua.startup.script=$(SciteDefaultHome)/luainspect/extman/extman.lua 94 | 95 | That normally is all you need to do. 96 | 97 | If you placed LuaInspect somewhere else or are using your own version 98 | of SciTE ExtMan (extman.lua), you will need to adjust the above to 99 | reference the absolute path where extman.lua is installed. LuaInspect 100 | includes its own copy of SciTE ExtMan 101 | , and it's recommended to use 102 | the included version because older versions might not work 103 | properly. The files in the scite_lua subfolder are not strictly 104 | necessary but are suggested. In particularly, scite_lua/luainspect.lua 105 | allows ExtMan to find LuaInspect, and you will need to adjust this if 106 | you move LuaInspect somewhere else relative to ExtMan. 107 | 108 | Dependencies: 109 | Tested with SciTE version 2.12/2.20 (older versions might not work). 110 | Requires http://lua-users.org/wiki/SciteExtMan (version included). 111 | Note: ExtMan's ctagsdx.lua is recommended (allows "goto mark" 112 | command to return to previous location following a "go to 113 | definition" or "show all variable uses"). 114 | 115 | If you want to customize styles, add the contents of the 116 | `light_styles` or `dark_styles` variable in the scite.lua file to a 117 | SciTE properties file. 118 | 119 | == Configuring SciTE options == 120 | 121 | The following LuaInspect options can be configured in one of your 122 | SciTE properties files: 123 | 124 | luainspect.update.always (0 or 1, default 1) 125 | luainspect.delay.count (integer >= 1, default 5) 126 | luainspect.annotate.all.locals (0 or 1, default 0) 127 | luainspect.incremental.compilation (0 or 1, default 1) 128 | luainspect.performance.tests (0 or 1, default 0) 129 | luainspect.autocomplete.vars (0 or 1, default 0) 130 | luainspect.autocomplete.syntax (0 or 1, default 0) 131 | luainspect.path.append (string, default '') 132 | luainspect.cpath.append (string, default '') 133 | style.script_lua.scheme (string, '' or 'dark', default '') 134 | 135 | For details, see scite.lua. 136 | 137 | == Installation on VIM == 138 | 139 | See [2] for VIM editor support. 140 | 141 | == Preliminary support for luaanalyze style comments == 142 | 143 | To make all variables in scope match name 'ast$' be recognized by LuaInspect as a 144 | table with field 'tag' of type string, add this to your code: 145 | 146 | --! context.apply_value('ast$', {tag=''}) 147 | 148 | The LuaInspect code itself uses this: 149 | 150 | --! require 'luainspect.typecheck' (context) 151 | 152 | == Design Notes == 153 | 154 | The font styles are intended to make the more dangerous 155 | or questionable code stand out more. 156 | 157 | Local variables named '_' are ignored for purposes of unused/masking variable 158 | reporting. Typical use case: `for _, v in ipairs(t) do <. . .> end`. 159 | 160 | == LICENSE == 161 | 162 | See LICENSE file. 163 | 164 | == Credits == 165 | 166 | David Manura, original author. 167 | Steve Donovan for discussions on design, SciTE and ExtMan. 168 | Fabien Fleutot for Metalua and discussions. 169 | SciTE suggestions/fixes by Tymur Gubayev. 170 | Peter Odding for VIM editor support [2]. 171 | Jon Akhtar - csv output and IntelliJ discussions. 172 | 173 | == Bugs == 174 | 175 | Please report bugs via github 176 | or just "dee em dot el you ae at em ae tee ayche two dot ow ar gee", or 177 | if you prefer neither then append to the wiki page 178 | . 179 | 180 | == References == 181 | 182 | [1] http://www.scintilla.org/SciTE.html 183 | [2] http://peterodding.com/code/vim/lua-inspect/ - VIM editor support 184 | -------------------------------------------------------------------------------- /misc/luainspect/luainspect/luainspect/command.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | -- luainspect.command - LuaInspect command-line interface. 4 | -- This file can be invoked from the command line 5 | 6 | package.path = package.path .. ';metalualib/?.lua' 7 | package.path = package.path .. ';lib/?.lua' 8 | 9 | 10 | local LA = require "luainspect.ast" 11 | local LI = require "luainspect.init" 12 | 13 | local function loadfile(filename) 14 | local fh = assert(io.open(filename, 'r')) 15 | local data = fh:read'*a' 16 | fh:close() 17 | return data 18 | end 19 | 20 | local function writefile(filename, output) 21 | local fh = assert(io.open(filename, 'wb')) 22 | fh:write(output) 23 | fh:close() 24 | end 25 | 26 | local function fail(err) 27 | io.stderr:write(err, '\n') 28 | os.exit(1) 29 | end 30 | 31 | -- Warning/status reporting function. 32 | -- CATEGORY: reporting + AST 33 | local function report(s) io.stderr:write(s, "\n") end 34 | 35 | -- parse flags 36 | local function getopt(c) 37 | if arg[1] then 38 | local x = arg[1]:match('^%-'..c..'(.*)') 39 | if x then table.remove(arg, 1) 40 | if x == '' and arg[1] then x = arg[1]; table.remove(arg, 1) end 41 | return x 42 | end 43 | end 44 | end 45 | local fmt = getopt 'f' or 'delimited' 46 | local ast_to_text = 47 | (fmt == 'delimited') and require 'luainspect.delimited'.ast_to_delimited or 48 | (fmt == 'html') and require 'luainspect.html'.ast_to_html or 49 | fail('invalid format specified, -f'..fmt) 50 | local libpath = getopt 'l' or '.' 51 | local outpath = getopt 'o' or '-' 52 | 53 | local path = unpack(arg) 54 | if not path then 55 | fail[[ 56 | inspect.lua [options] 57 | -f {delimited|html} - output format 58 | -l path path to library sources (e.g. luainspect.css/js), for html only 59 | -o path output path (defaults to standard output (-) 60 | ]] 61 | end 62 | 63 | local src = loadfile(path) 64 | local ast, err, linenum, colnum, linenum2 = LA.ast_from_string(src, path) 65 | 66 | --require "metalua.table2"; table.print(ast, 'hash', 50) 67 | if ast then 68 | local tokenlist = LA.ast_to_tokenlist(ast, src) 69 | LI.inspect(ast, tokenlist, src, report) 70 | LI.mark_related_keywords(ast, tokenlist, src) 71 | 72 | local output = ast_to_text(ast, src, tokenlist, {libpath=libpath}) 73 | 74 | if outpath == '-' then 75 | io.stdout:write(output) 76 | else 77 | writefile(outpath, output) 78 | end 79 | else 80 | io.stderr:write("syntax error: ", err) 81 | os.exit(1) 82 | end 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /misc/luainspect/luainspect/luainspect/compat_env.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | compat_env v$(_VERSION) - Lua 5.1/5.2 environment compatibility functions 4 | 5 | SYNOPSIS 6 | 7 | -- Get load/loadfile compatibility functions only if using 5.1. 8 | local CL = pcall(load, '') and _G or require 'compat_env' 9 | local load = CL.load 10 | local loadfile = CL.loadfile 11 | 12 | -- The following now works in both Lua 5.1 and 5.2: 13 | assert(load('return 2*pi', nil, 't', {pi=math.pi}))() 14 | assert(loadfile('ex.lua', 't', {print=print}))() 15 | 16 | -- Get getfenv/setfenv compatibility functions only if using 5.2. 17 | local getfenv = _G.getfenv or require 'compat_env'.getfenv 18 | local setfenv = _G.setfenv or require 'compat_env'.setfenv 19 | local function f() return x end 20 | setfenv(f, {x=2}) 21 | print(x, getfenv(f).x) --> 2, 2 22 | 23 | DESCRIPTION 24 | 25 | This module provides Lua 5.1/5.2 environment related compatibility functions. 26 | This includes implementations of Lua 5.2 style `load` and `loadfile` 27 | for use in Lua 5.1. It also includes Lua 5.1 style `getfenv` and `setfenv` 28 | for use in Lua 5.2. 29 | 30 | API 31 | 32 | local CL = require 'compat_env' 33 | 34 | CL.load (ld [, source [, mode [, env] ] ]) --> f [, err] 35 | 36 | This behaves the same as the Lua 5.2 `load` in both 37 | Lua 5.1 and 5.2. 38 | http://www.lua.org/manual/5.2/manual.html#pdf-load 39 | 40 | CL.loadfile ([filename [, mode [, env] ] ]) --> f [, err] 41 | 42 | This behaves the same as the Lua 5.2 `loadfile` in both 43 | Lua 5.1 and 5.2. 44 | http://www.lua.org/manual/5.2/manual.html#pdf-loadfile 45 | 46 | CL.getfenv ([f]) --> t 47 | 48 | This is identical to the Lua 5.1 `getfenv` in Lua 5.1. 49 | This behaves similar to the Lua 5.1 `getfenv` in Lua 5.2. 50 | When a global environment is to be returned, or when `f` is a 51 | C function, this returns `_G` since Lua 5.2 doesn't have 52 | (thread) global and C function environments. This will also 53 | return `_G` if the Lua function `f` lacks an `_ENV` 54 | upvalue, but it will raise an error if uncertain due to lack of 55 | debug info. It is not normally considered good design to use 56 | this function; when possible, use `load` or `loadfile` instead. 57 | http://www.lua.org/manual/5.1/manual.html#pdf-getfenv 58 | 59 | CL.setfenv (f, t) 60 | 61 | This is identical to the Lua 5.1 `setfenv` in Lua 5.1. 62 | This behaves similar to the Lua 5.1 `setfenv` in Lua 5.2. 63 | This will do nothing if `f` is a Lua function that 64 | lacks an `_ENV` upvalue, but it will raise an error if uncertain 65 | due to lack of debug info. See also Design Notes below. 66 | It is not normally considered good design to use 67 | this function; when possible, use `load` or `loadfile` instead. 68 | http://www.lua.org/manual/5.1/manual.html#pdf-setfenv 69 | 70 | DESIGN NOTES 71 | 72 | This module intends to provide robust and fairly complete reimplementations 73 | of the environment related Lua 5.1 and Lua 5.2 functions. 74 | No effort is made, however, to simulate rare or difficult to simulate features, 75 | such as thread environments, although this is liable to change in the future. 76 | Such 5.1 capabilities are discouraged and ideally 77 | removed from 5.1 code, thereby allowing your code to work in both 5.1 and 5.2. 78 | 79 | In Lua 5.2, a `setfenv(f, {})`, where `f` lacks any upvalues, will be silently 80 | ignored since there is no `_ENV` in this function to write to, and the 81 | environment will have no effect inside the function anyway. However, 82 | this does mean that `getfenv(setfenv(f, t))` does not necessarily equal `t`, 83 | which is incompatible with 5.1 code (a possible workaround would be [1]). 84 | If `setfenv(f, {})` has an upvalue but no debug info, then this will raise 85 | an error to prevent inadvertently executing potentially untrusted code in the 86 | global environment. 87 | 88 | It is not normally considered good design to use `setfenv` and `getfenv` 89 | (one reason they were removed in 5.2). When possible, consider replacing 90 | these with `load` or `loadfile`, which are more restrictive and have native 91 | implementations in 5.2. 92 | 93 | This module might be merged into a more general Lua 5.1/5.2 compatibility 94 | library (e.g. a full reimplementation of Lua 5.2 `_G`). However, 95 | `load/loadfile/getfenv/setfenv` perhaps are among the more cumbersome 96 | functions not to have. 97 | 98 | INSTALLATION 99 | 100 | Download compat_env.lua: 101 | 102 | wget https://raw.github.com/gist/1654007/compat_env.lua 103 | 104 | Copy compat_env.lua into your LUA_PATH. 105 | 106 | Alternately, unpack, test, and install into LuaRocks: 107 | 108 | wget https://raw.github.com/gist/1422205/sourceunpack.lua 109 | lua sourceunpack.lua compat_env.lua 110 | (cd out && luarocks make) 111 | 112 | Related work 113 | 114 | http://lua-users.org/wiki/LuaVersionCompatibility 115 | https://github.com/stevedonovan/Penlight/blob/master/lua/pl/utils.lua 116 | - penlight implementations of getfenv/setfenv 117 | http://lua-users.org/lists/lua-l/2010-06/msg00313.html 118 | - initial getfenv/setfenv implementation 119 | 120 | References 121 | 122 | [1] http://lua-users.org/lists/lua-l/2010-06/msg00315.html 123 | 124 | Copyright 125 | 126 | (c) 2012 David Manura. Licensed under the same terms as Lua 5.1/5.2 (MIT license). 127 | 128 | Permission is hereby granted, free of charge, to any person obtaining a copy 129 | of this software and associated documentation files (the "Software"), to deal 130 | in the Software without restriction, including without limitation the rights 131 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 132 | copies of the Software, and to permit persons to whom the Software is 133 | furnished to do so, subject to the following conditions: 134 | 135 | The above copyright notice and this permission notice shall be included in 136 | all copies or substantial portions of the Software. 137 | 138 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 139 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 140 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 141 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 142 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 143 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 144 | THE SOFTWARE. 145 | 146 | --]]--------------------------------------------------------------------- 147 | 148 | local M = {_TYPE='module', _NAME='compat_env', _VERSION='0.2.20120124'} 149 | 150 | local function check_chunk_type(s, mode) 151 | local nmode = mode or 'bt' 152 | local is_binary = s and #s > 0 and s:byte(1) == 27 153 | if is_binary and not nmode:match'b' then 154 | return nil, ("attempt to load a binary chunk (mode is '%s')"):format(mode) 155 | elseif not is_binary and not nmode:match't' then 156 | return nil, ("attempt to load a text chunk (mode is '%s')"):format(mode) 157 | end 158 | return true 159 | end 160 | 161 | local IS_52_LOAD = pcall(load, '') 162 | if IS_52_LOAD then 163 | M.load = _G.load 164 | M.loadfile = _G.loadfile 165 | else 166 | -- 5.2 style `load` implemented in 5.1 167 | function M.load(ld, source, mode, env) 168 | local f 169 | if type(ld) == 'string' then 170 | local s = ld 171 | local ok, err = check_chunk_type(s, mode); if not ok then return ok, err end 172 | local err; f, err = loadstring(s, source); if not f then return f, err end 173 | elseif type(ld) == 'function' then 174 | local ld2 = ld 175 | if (mode or 'bt') ~= 'bt' then 176 | local first = ld() 177 | local ok, err = check_chunk_type(first, mode); if not ok then return ok, err end 178 | ld2 = function() 179 | if first then 180 | local chunk=first; first=nil; return chunk 181 | else return ld() end 182 | end 183 | end 184 | local err; f, err = load(ld2, source); if not f then return f, err end 185 | else 186 | error(("bad argument #1 to 'load' (function expected, got %s)"):format(type(ld)), 2) 187 | end 188 | if env then setfenv(f, env) end 189 | return f 190 | end 191 | 192 | -- 5.2 style `loadfile` implemented in 5.1 193 | function M.loadfile(filename, mode, env) 194 | if (mode or 'bt') ~= 'bt' then 195 | local ioerr 196 | local fh, err = io.open(filename, 'rb'); if not fh then return fh, err end 197 | local function ld() local chunk; chunk,ioerr = fh:read(4096); return chunk end 198 | local f, err = M.load(ld, filename and '@'..filename, mode, env) 199 | fh:close() 200 | if not f then return f, err end 201 | if ioerr then return nil, ioerr end 202 | return f 203 | else 204 | local f, err = loadfile(filename); if not f then return f, err end 205 | if env then setfenv(f, env) end 206 | return f 207 | end 208 | end 209 | end 210 | 211 | if _G.setfenv then -- Lua 5.1 212 | M.setfenv = _G.setfenv 213 | M.getfenv = _G.getfenv 214 | else -- >= Lua 5.2 215 | -- helper function for `getfenv`/`setfenv` 216 | local function envlookup(f) 217 | local name, val 218 | local up = 0 219 | local unknown 220 | repeat 221 | up=up+1; name, val = debug.getupvalue(f, up) 222 | if name == '' then unknown = true end 223 | until name == '_ENV' or name == nil 224 | if name ~= '_ENV' then 225 | up = nil 226 | if unknown then error("upvalues not readable in Lua 5.2 when debug info missing", 3) end 227 | end 228 | return (name == '_ENV') and up, val, unknown 229 | end 230 | 231 | -- helper function for `getfenv`/`setfenv` 232 | local function envhelper(f, name) 233 | if type(f) == 'number' then 234 | if f < 0 then 235 | error(("bad argument #1 to '%s' (level must be non-negative)"):format(name), 3) 236 | elseif f < 1 then 237 | error("thread environments unsupported in Lua 5.2", 3) --[*] 238 | end 239 | f = debug.getinfo(f+2, 'f').func 240 | elseif type(f) ~= 'function' then 241 | error(("bad argument #1 to '%s' (number expected, got %s)"):format(type(name, f)), 2) 242 | end 243 | return f 244 | end 245 | -- [*] might simulate with table keyed by coroutine.running() 246 | 247 | -- 5.1 style `setfenv` implemented in 5.2 248 | function M.setfenv(f, t) 249 | local f = envhelper(f, 'setfenv') 250 | local up, val, unknown = envlookup(f) 251 | if up then 252 | debug.upvaluejoin(f, up, function() return up end, 1) -- unique upvalue [*] 253 | debug.setupvalue(f, up, t) 254 | else 255 | local what = debug.getinfo(f, 'S').what 256 | if what ~= 'Lua' and what ~= 'main' then -- not Lua func 257 | error("'setfenv' cannot change environment of given object", 2) 258 | end -- else ignore no _ENV upvalue (warning: incompatible with 5.1) 259 | end 260 | end 261 | -- [*] http://lua-users.org/lists/lua-l/2010-06/msg00313.html 262 | 263 | -- 5.1 style `getfenv` implemented in 5.2 264 | function M.getfenv(f) 265 | if f == 0 or f == nil then return _G end -- simulated behavior 266 | local f = envhelper(f, 'setfenv') 267 | local up, val = envlookup(f) 268 | if not up then return _G end -- simulated behavior [**] 269 | return val 270 | end 271 | -- [**] possible reasons: no _ENV upvalue, C function 272 | end 273 | 274 | 275 | return M 276 | 277 | --[[ FILE rockspec.in 278 | 279 | package = 'compat_env' 280 | version = '$(_VERSION)-1' 281 | source = { 282 | url = 'https://raw.github.com/gist/1654007/$(GITID)/compat_env.lua', 283 | --url = 'https://raw.github.com/gist/1654007/compat_env.lua', -- latest raw 284 | --url = 'https://gist.github.com/gists/1654007/download', 285 | md5 = '$(MD5)' 286 | } 287 | description = { 288 | summary = 'Lua 5.1/5.2 environment compatibility functions', 289 | detailed = [=[ 290 | Provides Lua 5.1/5.2 environment related compatibility functions. 291 | This includes implementations of Lua 5.2 style `load` and `loadfile` 292 | for use in Lua 5.1. It also includes Lua 5.1 style `getfenv` and `setfenv` 293 | for use in Lua 5.2. 294 | ]=], 295 | license = 'MIT/X11', 296 | homepage = 'https://gist.github.com/1654007', 297 | maintainer = 'David Manura' 298 | } 299 | dependencies = {} -- Lua 5.1 or 5.2 300 | build = { 301 | type = 'builtin', 302 | modules = { 303 | ['compat_env'] = 'compat_env.lua' 304 | } 305 | } 306 | 307 | --]]--------------------------------------------------------------------- 308 | 309 | --[[ FILE test.lua 310 | 311 | -- test.lua - test suite for compat_env module. 312 | 313 | local CL = require 'compat_env' 314 | local load = CL.load 315 | local loadfile = CL.loadfile 316 | local setfenv = CL.setfenv 317 | local getfenv = CL.getfenv 318 | 319 | local function checkeq(a, b, e) 320 | if a ~= b then error( 321 | 'not equal ['..tostring(a)..'] ['..tostring(b)..'] ['..tostring(e)..']') 322 | end 323 | end 324 | local function checkerr(pat, ok, err) 325 | assert(not ok, 'checkerr') 326 | assert(type(err) == 'string' and err:match(pat), err) 327 | end 328 | 329 | -- test `load` 330 | checkeq(load('return 2')(), 2) 331 | checkerr('expected near', load'return 2 2') 332 | checkerr('text chunk', load('return 2', nil, 'b')) 333 | checkerr('text chunk', load('', nil, 'b')) 334 | checkerr('binary chunk', load('\027', nil, 't')) 335 | checkeq(load('return 2*x',nil,'bt',{x=5})(), 10) 336 | checkeq(debug.getinfo(load('')).source, '') 337 | checkeq(debug.getinfo(load('', 'foo')).source, 'foo') 338 | 339 | -- test `loadfile` 340 | local fh = assert(io.open('tmp.lua', 'wb')) 341 | fh:write('return (...) or x') 342 | fh:close() 343 | checkeq(loadfile('tmp.lua')(2), 2) 344 | checkeq(loadfile('tmp.lua', 't')(2), 2) 345 | checkerr('text chunk', loadfile('tmp.lua', 'b')) 346 | checkeq(loadfile('tmp.lua', nil, {x=3})(), 3) 347 | checkeq(debug.getinfo(loadfile('tmp.lua')).source, '@tmp.lua') 348 | checkeq(debug.getinfo(loadfile('tmp.lua', 't', {})).source, '@tmp.lua') 349 | os.remove'tmp.lua' 350 | 351 | -- test `setfenv`/`getfenv` 352 | x = 5 353 | local a,b=true; local function f(c) if a then return x,b,c end end 354 | setfenv(f, {x=3}) 355 | checkeq(f(), 3) 356 | checkeq(getfenv(f).x, 3) 357 | checkerr('cannot change', pcall(setfenv, string.len, {})) -- C function 358 | checkeq(getfenv(string.len), _G) -- C function 359 | local function g() 360 | setfenv(1, {x=4}) 361 | checkeq(getfenv(1).x, 4) 362 | return x 363 | end 364 | checkeq(g(), 4) -- numeric level 365 | if _G._VERSION ~= 'Lua 5.1' then 366 | checkerr('unsupported', pcall(setfenv, 0, {})) 367 | end 368 | checkeq(getfenv(0), _G) 369 | checkeq(getfenv(), _G) -- no arg 370 | checkeq(x, 5) -- main unaltered 371 | setfenv(function()end, {}) -- no upvalues, ignore 372 | checkeq(getfenv(function()end), _G) -- no upvaluse 373 | if _G._VERSION ~= 'Lua 5.1' then 374 | checkeq(getfenv(setfenv(function()end, {})), _G) -- warning: incompatible with 5.1 375 | end 376 | x = nil 377 | 378 | print 'OK' 379 | 380 | --]]--------------------------------------------------------------------- 381 | 382 | --[[ FILE CHANGES.txt 383 | 0.2.20120124 384 | Renamed module to compat_env (from compat_load) 385 | Add getfenv/setfenv functions 386 | 387 | 0.1.20120121 388 | Initial public release 389 | --]] 390 | 391 | -------------------------------------------------------------------------------- /misc/luainspect/luainspect/luainspect/delimited.lua: -------------------------------------------------------------------------------- 1 | -- luainspect.delimited - Convert AST to delimited text using LuaInspect info embedded. 2 | -- 3 | 4 | --! require 'luainspect.typecheck' (context) 5 | 6 | local M = {} 7 | 8 | local LI = require"luainspect.init" 9 | 10 | 11 | local function escape(s) 12 | -- s = s:gsub('\n', '\\n') -- escape new lines 13 | s = s:gsub('"', '""') -- escape double quotes 14 | if s:match'["\r\n,]' then s = '"'..s..'"' end -- escape with double quotes 15 | return s 16 | end 17 | 18 | 19 | local function describe(token, tokenlist, src) 20 | if token then 21 | local ast = token.ast 22 | if token.tag == 'Id' or ast.isfield then 23 | local line = 'id' 24 | if ast.id then line = line .. ",id" .. ast.id end 25 | line = line .. ',' .. escape(table.concat(LI.get_var_attributes(ast),' ')) 26 | line = line .. ',' .. escape(LI.get_value_details(ast, tokenlist, src):gsub('\n', ';')) 27 | return line 28 | end 29 | end 30 | end 31 | 32 | 33 | function M.ast_to_delimited(ast, src, tokenlist) 34 | local fmt_tokens = {} 35 | for _, token in ipairs(tokenlist) do 36 | local fchar, lchar = token.fpos, token.lpos 37 | local desc = describe(token, tokenlist, src) 38 | if desc then 39 | fmt_tokens[#fmt_tokens + 1] = ("%d,%d,%s\n"):format(fchar, lchar, desc) 40 | end 41 | end 42 | return table.concat(fmt_tokens) 43 | end 44 | 45 | 46 | return M 47 | -------------------------------------------------------------------------------- /misc/luainspect/luainspect/luainspect/dump.lua: -------------------------------------------------------------------------------- 1 | -- Recursive object dumper, for debugging. 2 | -- (c) 2010 David Manura, MIT License. 3 | 4 | local M = {} 5 | 6 | -- My own object dumper. 7 | -- Intended for debugging, not serialization, with compact formatting. 8 | -- Robust against recursion. 9 | -- Renders Metalua table tag fields specially {tag=X, ...} --> "`X{...}". 10 | -- On first call, only pass parameter o. 11 | -- CATEGORY: AST debug 12 | local ignore_keys_ = {lineinfo=true} 13 | local norecurse_keys_ = {parent=true, ast=true} 14 | local function dumpstring_key_(k, isseen, newindent) 15 | local ks = type(k) == 'string' and k:match'^[%a_][%w_]*$' and k or 16 | '[' .. M.dumpstring(k, isseen, newindent) .. ']' 17 | return ks 18 | end 19 | local function sort_keys_(a, b) 20 | if type(a) == 'number' and type(b) == 'number' then 21 | return a < b 22 | elseif type(a) == 'number' then 23 | return false 24 | elseif type(b) == 'number' then 25 | return true 26 | elseif type(a) == 'string' and type(b) == 'string' then 27 | return a < b 28 | else 29 | return tostring(a) < tostring(b) -- arbitrary 30 | end 31 | end 32 | function M.dumpstring(o, isseen, indent, key) 33 | isseen = isseen or {} 34 | indent = indent or '' 35 | 36 | if type(o) == 'table' then 37 | if isseen[o] or norecurse_keys_[key] then 38 | return (type(o.tag) == 'string' and '`' .. o.tag .. ':' or '') .. tostring(o) 39 | else isseen[o] = true end -- avoid recursion 40 | 41 | local used = {} 42 | 43 | local tag = o.tag 44 | local s = '{' 45 | if type(o.tag) == 'string' then 46 | s = '`' .. tag .. s; used['tag'] = true 47 | end 48 | local newindent = indent .. ' ' 49 | 50 | local ks = {}; for k in pairs(o) do ks[#ks+1] = k end 51 | table.sort(ks, sort_keys_) 52 | --for i,k in ipairs(ks) do print ('keys', k) end 53 | 54 | local forcenummultiline 55 | for k in pairs(o) do 56 | if type(k) == 'number' and type(o[k]) == 'table' then forcenummultiline = true end 57 | end 58 | 59 | -- inline elements 60 | for _,k in ipairs(ks) do 61 | if used[k] then -- skip 62 | elseif ignore_keys_[k] then used[k] = true 63 | elseif (type(k) ~= 'number' or not forcenummultiline) and 64 | type(k) ~= 'table' and (type(o[k]) ~= 'table' or norecurse_keys_[k]) 65 | then 66 | s = s .. dumpstring_key_(k, isseen, newindent) .. '=' .. M.dumpstring(o[k], isseen, newindent, k) .. ', ' 67 | used[k] = true 68 | end 69 | end 70 | 71 | -- elements on separate lines 72 | local done 73 | for _,k in ipairs(ks) do 74 | if not used[k] then 75 | if not done then s = s .. '\n'; done = true end 76 | s = s .. newindent .. dumpstring_key_(k, isseen) .. '=' .. M.dumpstring(o[k], isseen, newindent, k) .. ',\n' 77 | end 78 | end 79 | s = s:gsub(',(%s*)$', '%1') 80 | s = s .. (done and indent or '') .. '}' 81 | return s 82 | elseif type(o) == 'string' then 83 | return string.format('%q', o) 84 | else 85 | return tostring(o) 86 | end 87 | end 88 | 89 | return M 90 | 91 | -------------------------------------------------------------------------------- /misc/luainspect/luainspect/luainspect/globals.lua: -------------------------------------------------------------------------------- 1 | -- LuaInspect.globals - identifier scope analysis 2 | -- Locates locals, globals, and their definitions. 3 | -- 4 | -- (c) D.Manura, 2008-2010, MIT license. 5 | 6 | -- based on http://lua-users.org/wiki/DetectingUndefinedVariables 7 | 8 | local M = {} 9 | 10 | --! require 'luainspect.typecheck' (context) 11 | 12 | local LA = require "luainspect.ast" 13 | 14 | local function definelocal(scope, name, ast) 15 | if scope[name] then 16 | scope[name].localmasked = true 17 | ast.localmasking = scope[name] 18 | end 19 | scope[name] = ast 20 | if name == '_' then ast.isignore = true end 21 | end 22 | 23 | -- Resolves scoping and usages of variable in AST. 24 | -- Data Notes: 25 | -- ast.localdefinition refers to lexically scoped definition of `Id node `ast`. 26 | -- If ast.localdefinition == ast then ast is a "lexical definition". 27 | -- If ast.localdefinition == nil, then variable is global. 28 | -- ast.functionlevel is the number of functions the AST is contained in. 29 | -- ast.functionlevel is defined iff ast is a lexical definition. 30 | -- ast.isparam is true iff ast is a lexical definition and a function parameter. 31 | -- ast.isset is true iff ast is a lexical definition and exists an assignment on it. 32 | -- ast.isused is true iff ast is a lexical definition and has been referred to. 33 | -- ast.isignore is true if local variable should be ignored (e.g. typically "_") 34 | -- ast.localmasking - for a lexical definition, this is set to the lexical definition 35 | -- this is masking (i.e. same name). nil if not masking. 36 | -- ast.localmasked - true iff lexical definition masked by another lexical definition. 37 | -- ast.isfield is true iff `String node ast is used for field access on object, 38 | -- e.g. x.y or x['y'].z 39 | -- ast.previous - For `Index{o,s} or `Invoke{o,s,...}, s.previous == o 40 | local function traverse(ast, scope, globals, level, functionlevel) 41 | scope = scope or {} 42 | 43 | local blockrecurse 44 | 45 | -- operations on walking down the AST 46 | if ast.tag == 'Local' then 47 | blockrecurse = 1 48 | -- note: apply new scope after processing values 49 | elseif ast.tag == 'Localrec' then 50 | local namelist_ast, valuelist_ast = ast[1], ast[2] 51 | for _,value_ast in ipairs(namelist_ast) do 52 | assert(value_ast.tag == 'Id') 53 | local name = value_ast[1] 54 | local parentscope = getmetatable(scope).__index 55 | definelocal(parentscope, name, value_ast) 56 | value_ast.localdefinition = value_ast 57 | value_ast.functionlevel = functionlevel 58 | end 59 | blockrecurse = 1 60 | elseif ast.tag == 'Id' then 61 | local name = ast[1] 62 | if scope[name] then 63 | ast.localdefinition = scope[name] 64 | ast.functionlevel = functionlevel 65 | scope[name].isused = true 66 | else -- global, do nothing 67 | end 68 | elseif ast.tag == 'Function' then 69 | local paramlist_ast, body_ast = ast[1], ast[2] 70 | functionlevel = functionlevel + 1 71 | for _,param_ast in ipairs(paramlist_ast) do 72 | local name = param_ast[1] 73 | assert(param_ast.tag == 'Id' or param_ast.tag == 'Dots') 74 | if param_ast.tag == 'Id' then 75 | definelocal(scope, name, param_ast) 76 | param_ast.localdefinition = param_ast 77 | param_ast.functionlevel = functionlevel 78 | param_ast.isparam = true 79 | end 80 | end 81 | blockrecurse = 1 82 | elseif ast.tag == 'Set' then 83 | local reflist_ast, valuelist_ast = ast[1], ast[2] 84 | for _,ref_ast in ipairs(reflist_ast) do 85 | if ref_ast.tag == 'Id' then 86 | local name = ref_ast[1] 87 | if scope[name] then 88 | scope[name].isset = true 89 | else 90 | if not globals[name] then 91 | globals[name] = {set=ref_ast} 92 | end 93 | end 94 | end 95 | end 96 | --ENHANCE? We could differentiate assignments to x (which indicates that 97 | -- x is not const) and assignments to a member of x (which indicates that 98 | -- x is not a pointer to const) and assignments to any nested member of x 99 | -- (which indicates that x it not a transitive const). 100 | elseif ast.tag == 'Fornum' then 101 | blockrecurse = 1 102 | elseif ast.tag == 'Forin' then 103 | blockrecurse = 1 104 | end 105 | 106 | -- recurse (depth-first search down the AST) 107 | if ast.tag == 'Repeat' then 108 | local block_ast, cond_ast = ast[1], ast[2] 109 | local scope = scope 110 | for _,stat_ast in ipairs(block_ast) do 111 | scope = setmetatable({}, {__index = scope}) 112 | traverse(stat_ast, scope, globals, level+1, functionlevel) 113 | end 114 | scope = setmetatable({}, {__index = scope}) 115 | traverse(cond_ast, scope, globals, level+1, functionlevel) 116 | elseif ast.tag == 'Fornum' then 117 | local name_ast, block_ast = ast[1], ast[#ast] 118 | -- eval value list in current scope 119 | for i=2, #ast-1 do traverse(ast[i], scope, globals, level+1, functionlevel) end 120 | -- eval body in next scope 121 | local name = name_ast[1] 122 | definelocal(scope, name, name_ast) 123 | name_ast.localdefinition = name_ast 124 | name_ast.functionlevel = functionlevel 125 | traverse(block_ast, scope, globals, level+1, functionlevel) 126 | elseif ast.tag == 'Forin' then 127 | local namelist_ast, vallist_ast, block_ast = ast[1], ast[2], ast[3] 128 | -- eval value list in current scope 129 | traverse(vallist_ast, scope, globals, level+1, functionlevel) 130 | -- eval body in next scope 131 | for _,name_ast in ipairs(namelist_ast) do 132 | local name = name_ast[1] 133 | definelocal(scope, name, name_ast) 134 | name_ast.localdefinition = name_ast 135 | name_ast.functionlevel = functionlevel 136 | end 137 | traverse(block_ast, scope, globals, level+1, functionlevel) 138 | else -- normal 139 | for i,v in ipairs(ast) do 140 | if i ~= blockrecurse and type(v) == 'table' then 141 | local scope = setmetatable({}, {__index = scope}) 142 | traverse(v, scope, globals, level+1, functionlevel) 143 | end 144 | end 145 | end 146 | 147 | -- operations on walking up the AST 148 | if ast.tag == 'Local' then 149 | -- Unlike Localrec, variables come into scope after evaluating values. 150 | local namelist_ast, valuelist_ast = ast[1], ast[2] 151 | for _,name_ast in ipairs(namelist_ast) do 152 | assert(name_ast.tag == 'Id') 153 | local name = name_ast[1] 154 | local parentscope = getmetatable(scope).__index 155 | definelocal(parentscope, name, name_ast) 156 | name_ast.localdefinition = name_ast 157 | name_ast.functionlevel = functionlevel 158 | end 159 | elseif ast.tag == 'Index' then 160 | if ast[2].tag == 'String' then 161 | ast[2].isfield = true 162 | ast[2].previous = ast[1] 163 | end 164 | elseif ast.tag == 'Invoke' then 165 | assert(ast[2].tag == 'String') 166 | ast[2].isfield = true 167 | ast[2].previous = ast[1] 168 | end 169 | end 170 | 171 | function M.globals(ast) 172 | -- Default list of defined variables. 173 | local scope = setmetatable({}, {}) 174 | local globals = {} 175 | traverse(ast, scope, globals, 1, 1) -- Start check. 176 | 177 | return globals 178 | end 179 | 180 | 181 | -- Gets locals in scope of statement of block ast. If isafter is true and ast is statement, 182 | -- uses scope just after statement ast. 183 | -- Assumes 'parent' attributes on ast are marked. 184 | -- Returns table mapping name -> AST local definition. 185 | function M.variables_in_scope(ast, isafter) 186 | local scope = {} 187 | local cast = ast 188 | while cast.parent do 189 | local midx = LA.ast_idx(cast.parent, cast) 190 | for idx=1,midx do 191 | local bast = cast.parent[idx] 192 | if bast.tag == 'Localrec' or bast.tag == 'Local' and (idx < midx or isafter) then 193 | local names_ast = bast[1] 194 | for bidx=1,#names_ast do 195 | local name_ast = names_ast[bidx] 196 | local name = name_ast[1] 197 | scope[name] = name_ast 198 | end 199 | elseif cast ~= ast and (bast.tag == 'For' or bast.tag == 'Forin' or bast.tag == 'Function') then 200 | local names_ast = bast[1] 201 | for bidx=1,#names_ast do 202 | local name_ast = names_ast[bidx] 203 | if name_ast.tag == 'Id' then --Q: or maybe `Dots should be included 204 | local name = name_ast[1] 205 | scope[name] = name_ast 206 | end 207 | end 208 | end 209 | end 210 | cast = cast.parent 211 | end 212 | return scope 213 | end 214 | 215 | 216 | return M 217 | -------------------------------------------------------------------------------- /misc/luainspect/luainspect/luainspect/html.lua: -------------------------------------------------------------------------------- 1 | -- luainspect.html - Convert AST to HTML using LuaInspect info embedded. 2 | -- 3 | -- (c) 2010 David Manura, MIT License. 4 | 5 | --! require 'luainspect.typecheck' (context) 6 | 7 | local M = {} 8 | 9 | local LI = require "luainspect.init" 10 | 11 | -- FIX!!! improve: should be registered utility function 12 | local function escape_html(s) 13 | return s:gsub('&', '&'):gsub('<', '<'):gsub('>', '>'):gsub('"', '"') 14 | end 15 | 16 | local function annotate_source(src, ast, tokenlist, emit) 17 | local start = 1 18 | local fmt_srcs = {} 19 | for _,token in ipairs(tokenlist) do 20 | local fchar, lchar = token.fpos, token.lpos 21 | if fchar > start then 22 | table.insert(fmt_srcs, emit(src:sub(start, fchar-1))) 23 | end 24 | table.insert(fmt_srcs, emit(src:sub(fchar, lchar), token)) 25 | start = lchar + 1 26 | end 27 | if start <= #src then 28 | table.insert(fmt_srcs, emit(src:sub(start))) 29 | end 30 | return table.concat(fmt_srcs) 31 | end 32 | 33 | function M.ast_to_html(ast, src, tokenlist, options) 34 | local src_html = annotate_source(src, ast, tokenlist, function(snip_src, token) 35 | local snip_html = escape_html(snip_src) 36 | if token then 37 | local ast = token.ast 38 | if token.tag == 'Id' or ast.isfield then 39 | local class = 'id ' 40 | class = class .. table.concat(LI.get_var_attributes(ast), " ") 41 | if ast.id then class = class.." id"..ast.id end 42 | local desc_html = escape_html(LI.get_value_details(ast, tokenlist, src)) 43 | if ast.lineinfo then 44 | local linenum = ast.lineinfo.first[1] 45 | desc_html = desc_html .. '\nused-line:' .. linenum 46 | end 47 | return ""..snip_html..""..desc_html.."" 48 | elseif token.tag == 'Comment' then 49 | return ""..snip_html.."" 50 | elseif token.tag == 'String' then -- note: excludes ast.isfield 51 | return ""..snip_html.."" 52 | elseif token.tag == 'Keyword' then 53 | local id = token.keywordid and 'idk'..tostring(token.keywordid) or '' 54 | return ""..snip_html.."" 55 | end 56 | end 57 | return snip_html 58 | end) 59 | 60 | 61 | local function get_line_numbers_html(src) 62 | local out_htmls = {} 63 | local linenum = 1 64 | for line in src:gmatch("[^\n]*\n?") do 65 | if line == "" then break end 66 | table.insert(out_htmls, string.format('%d:\n', linenum, linenum)) 67 | linenum = linenum + 1 68 | end 69 | return table.concat(out_htmls) 70 | end 71 | 72 | local line_numbers_html = get_line_numbers_html(src) 73 | 74 | options = options or {} 75 | local libpath = options.libpath or '.' 76 | 77 | src_html = [[ 78 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 |
91 |
]] .. line_numbers_html .. [[
92 |
]] .. src_html .. [[
93 |
94 |
95 | 96 | ]] 97 | 98 | return src_html 99 | end 100 | 101 | return M 102 | -------------------------------------------------------------------------------- /misc/luainspect/luainspect/luainspect/signatures.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local T = require "luainspect.types" 4 | 5 | -- signatures of known globals 6 | M.global_signatures = { 7 | assert = "assert (v [, message])", 8 | collectgarbage = "collectgarbage (opt [, arg])", 9 | dofile = "dofile (filename)", 10 | error = "error (message [, level])", 11 | _G = "(table)", 12 | getfenv = "getfenv ([f])", 13 | getmetatable = "getmetatable (object)", 14 | ipairs = "ipairs (t)", 15 | load = "load (func [, chunkname])", 16 | loadfile = "loadfile ([filename])", 17 | loadstring = "loadstring (string [, chunkname])", 18 | next = "next (table [, index])", 19 | pairs = "pairs (t)", 20 | pcall = "pcall (f, arg1, ...)", 21 | print = "print (...)", 22 | rawequal = "rawequal (v1, v2)", 23 | rawget = "rawget (table, index)", 24 | rawset = "rawset (table, index, value)", 25 | select = "select (index, ...)", 26 | setfenv = "setfenv (f, table)", 27 | setmetatable = "setmetatable (table, metatable)", 28 | tonumber = "tonumber (e [, base])", 29 | tostring = "tostring (e)", 30 | type = "type (v)", 31 | unpack = "unpack (list [, i [, j]])", 32 | _VERSION = "(string)", 33 | xpcall = "xpcall (f, err)", 34 | module = "module (name [, ...])", 35 | require = "require (modname)", 36 | coroutine = "(table) coroutine manipulation library", 37 | debug = "(table) debug facilities library", 38 | io = "(table) I/O library", 39 | math = "(table) math functions libary", 40 | os = "(table) OS facilities library", 41 | package = "(table) package library", 42 | string = "(table) string manipulation library", 43 | table = "(table) table manipulation library", 44 | ["coroutine.create"] = "coroutine.create (f)", 45 | ["coroutine.resume"] = "coroutine.resume (co [, val1, ...])", 46 | ["coroutine.running"] = "coroutine.running ()", 47 | ["coroutine.status"] = "coroutine.status (co)", 48 | ["coroutine.wrap"] = "coroutine.wrap (f)", 49 | ["coroutine.yield"] = "coroutine.yield (...)", 50 | ["debug.debug"] = "debug.debug ()", 51 | ["debug.getfenv"] = "debug.getfenv (o)", 52 | ["debug.gethook"] = "debug.gethook ([thread])", 53 | ["debug.getinfo"] = "debug.getinfo ([thread,] function [, what])", 54 | ["debug.getlocal"] = "debug.getlocal ([thread,] level, local)", 55 | ["debug.getmetatable"] = "debug.getmetatable (object)", 56 | ["debug.getregistry"] = "debug.getregistry ()", 57 | ["debug.getupvalue"] = "debug.getupvalue (func, up)", 58 | ["debug.setfenv"] = "debug.setfenv (object, table)", 59 | ["debug.sethook"] = "debug.sethook ([thread,] hook, mask [, count])", 60 | ["debug.setlocal"] = "debug.setlocal ([thread,] level, local, value)", 61 | ["debug.setmetatable"] = "debug.setmetatable (object, table)", 62 | ["debug.setupvalue"] = "debug.setupvalue (func, up, value)", 63 | ["debug.traceback"] = "debug.traceback ([thread,] [message] [, level])", 64 | ["io.close"] = "io.close ([file])", 65 | ["io.flush"] = "io.flush ()", 66 | ["io.input"] = "io.input ([file])", 67 | ["io.lines"] = "io.lines ([filename])", 68 | ["io.open"] = "io.open (filename [, mode])", 69 | ["io.output"] = "io.output ([file])", 70 | ["io.popen"] = "io.popen (prog [, mode])", 71 | ["io.read"] = "io.read (...)", 72 | ["io.tmpfile"] = "io.tmpfile ()", 73 | ["io.type"] = "io.type (obj)", 74 | ["io.write"] = "io.write (...)", 75 | ["math.abs"] = "math.abs (x)", 76 | ["math.acos"] = "math.acos (x)", 77 | ["math.asin"] = "math.asin (x)", 78 | ["math.atan"] = "math.atan (x)", 79 | ["math.atan2"] = "math.atan2 (y, x)", 80 | ["math.ceil"] = "math.ceil (x)", 81 | ["math.cos"] = "math.cos (x)", 82 | ["math.cosh"] = "math.cosh (x)", 83 | ["math.deg"] = "math.deg (x)", 84 | ["math.exp"] = "math.exp (x)", 85 | ["math.floor"] = "math.floor (x)", 86 | ["math.fmod"] = "math.fmod (x, y)", 87 | ["math.frexp"] = "math.frexp (x)", 88 | ["math.huge"] = "math.huge", 89 | ["math.ldexp"] = "math.ldexp (m, e)", 90 | ["math.log"] = "math.log (x)", 91 | ["math.log10"] = "math.log10 (x)", 92 | ["math.max"] = "math.max (x, ...)", 93 | ["math.min"] = "math.min (x, ...)", 94 | ["math.modf"] = "math.modf (x)", 95 | ["math.pi"] = "math.pi", 96 | ["math.pow"] = "math.pow (x, y)", 97 | ["math.rad"] = "math.rad (x)", 98 | ["math.random"] = "math.random ([m [, n]])", 99 | ["math.randomseed"] = "math.randomseed (x)", 100 | ["math.sin"] = "math.sin (x)", 101 | ["math.sinh"] = "math.sinh (x)", 102 | ["math.sqrt"] = "math.sqrt (x)", 103 | ["math.tan"] = "math.tan (x)", 104 | ["math.tanh"] = "math.tanh (x)", 105 | ["os.clock"] = "os.clock ()", 106 | ["os.date"] = "os.date ([format [, time]])", 107 | ["os.difftime"] = "os.difftime (t2, t1)", 108 | ["os.execute"] = "os.execute ([command])", 109 | ["os.exit"] = "os.exit ([code])", 110 | ["os.getenv"] = "os.getenv (varname)", 111 | ["os.remove"] = "os.remove (filename)", 112 | ["os.rename"] = "os.rename (oldname, newname)", 113 | ["os.setlocale"] = "os.setlocale (locale [, category])", 114 | ["os.time"] = "os.time ([table])", 115 | ["os.tmpname"] = "os.tmpname ()", 116 | ["package.cpath"] = "package.cpath", 117 | ["package.loaded"] = "package.loaded", 118 | ["package.loaders"] = "package.loaders", 119 | ["package.loadlib"] = "package.loadlib (libname, funcname)", 120 | ["package.path"] = "package.path", 121 | ["package.preload"] = "package.preload", 122 | ["package.seeall"] = "package.seeall (module)", 123 | ["string.byte"] = "string.byte (s [, i [, j]])", 124 | ["string.char"] = "string.char (...)", 125 | ["string.dump"] = "string.dump (function)", 126 | ["string.find"] = "string.find (s, pattern [, init [, plain]])", 127 | ["string.format"] = "string.format (formatstring, ...)", 128 | ["string.gmatch"] = "string.gmatch (s, pattern)", 129 | ["string.gsub"] = "string.gsub (s, pattern, repl [, n])", 130 | ["string.len"] = "string.len (s)", 131 | ["string.lower"] = "string.lower (s)", 132 | ["string.match"] = "string.match (s, pattern [, init])", 133 | ["string.rep"] = "string.rep (s, n)", 134 | ["string.reverse"] = "string.reverse (s)", 135 | ["string.sub"] = "string.sub (s, i [, j])", 136 | ["string.upper"] = "string.upper (s)", 137 | ["table.concat"] = "table.concat (table [, sep [, i [, j]]])", 138 | ["table.insert"] = "table.insert (table, [pos,] value)", 139 | ["table.maxn"] = "table.maxn (table)", 140 | ["table.remove"] = "table.remove (table [, pos])", 141 | ["table.sort"] = "table.sort (table [, comp])", 142 | } 143 | 144 | -- utility function. Converts e.g. name 'math.sqrt' to its value. 145 | local function resolve_global_helper_(name) 146 | local o = _G 147 | for fieldname in name:gmatch'[^%.]+' do o = o[fieldname] end 148 | return o 149 | end 150 | local function resolve_global(name) 151 | local a, b = pcall(resolve_global_helper_, name) 152 | if a then return b else return nil, b end 153 | end 154 | 155 | -- Same as global_signatures but maps value (not name) to signature. 156 | M.value_signatures = {} 157 | local isobject = {['function']=true, ['table']=true, ['userdata']=true, ['coroutine']=true} 158 | for name,sig in pairs(M.global_signatures) do 159 | local val, err = resolve_global(name) 160 | if isobject[type(val)] then 161 | M.value_signatures[val] = sig 162 | end 163 | end 164 | 165 | -- min,max argument counts. 166 | M.argument_counts = { 167 | [assert] = {1,2}, 168 | [collectgarbage] = {1,2}, 169 | [dofile] = {1}, 170 | [error] = {1,2}, 171 | [getfenv or false] = {0,1}, 172 | [getmetatable] = {1,1}, 173 | [ipairs] = {1,1}, 174 | [load] = {1,2}, 175 | [loadfile] = {0,1}, 176 | [loadstring] = {1,2}, 177 | [next] = {1,2}, 178 | [pairs] = {1,1}, 179 | [pcall] = {1,math.huge}, 180 | [print] = {0,math.huge}, 181 | [rawequal] = {2,2}, 182 | [rawget] = {2,2}, 183 | [rawset] = {3,3}, 184 | [select] = {1, math.huge}, 185 | [setfenv or false] = {2,2}, 186 | [setmetatable] = {2,2}, 187 | [tonumber] = {1,2}, 188 | [tostring] = {1}, 189 | [type] = {1}, 190 | [unpack] = {1,3}, 191 | [xpcall] = {2,2}, 192 | [module] = {1,math.huge}, 193 | [require] = {1,1}, 194 | [coroutine.create] = {1,1}, 195 | [coroutine.resume] = {1, math.huge}, 196 | [coroutine.running] = {0,0}, 197 | [coroutine.status] = {1,1}, 198 | [coroutine.wrap] = {1,1}, 199 | [coroutine.yield] = {0,math.huge}, 200 | [debug.debug] = {0,0}, 201 | [debug.getfenv or false] = {1,1}, 202 | [debug.gethook] = {0,1}, 203 | [debug.getinfo] = {1,3}, 204 | [debug.getlocal] = {2,3}, 205 | [debug.getmetatable] = {1,1}, 206 | [debug.getregistry] = {0,0}, 207 | [debug.getupvalue] = {2,2}, 208 | [debug.setfenv or false] = {2,2}, 209 | [debug.sethook] = {2,4}, 210 | [debug.setlocal] = {3,4}, 211 | [debug.setmetatable] = {2,2}, 212 | [debug.setupvalue] = {3,3}, 213 | [debug.traceback] = {0,3}, 214 | [io.close] = {0,1}, 215 | [io.flush] = {0,0}, 216 | [io.input] = {0,1}, 217 | [io.lines] = {0,1}, 218 | [io.open] = {1,2}, 219 | [io.output] = {0,1}, 220 | [io.popen] = {1,2}, 221 | [io.read] = {0,math.huge}, 222 | [io.tmpfile] = {0}, 223 | [io.type] = {1}, 224 | [io.write] = {0,math.huge}, 225 | [math.abs] = {1}, 226 | [math.acos] = {1}, 227 | [math.asin] = {1}, 228 | [math.atan] = {1}, 229 | [math.atan2] = {2,2}, 230 | [math.ceil] = {1,1}, 231 | [math.cos] = {1,1}, 232 | [math.cosh] = {1,1}, 233 | [math.deg] = {1,1}, 234 | [math.exp] = {1,1}, 235 | [math.floor] = {1,1}, 236 | [math.fmod] = {2,2}, 237 | [math.frexp] = {1,1}, 238 | [math.ldexp] = {2,2}, 239 | [math.log] = {1,1}, 240 | [math.log10] = {1,1}, 241 | [math.max] = {1,math.huge}, 242 | [math.min] = {1,math.huge}, 243 | [math.modf] = {1,1}, 244 | [math.pow] = {2,2}, 245 | [math.rad] = {1,1}, 246 | [math.random] = {0,2}, 247 | [math.randomseed] = {1,1}, 248 | [math.sin] = {1,1}, 249 | [math.sinh] = {1,1}, 250 | [math.sqrt] = {1,1}, 251 | [math.tan] = {1,1}, 252 | [math.tanh] = {1,1}, 253 | [os.clock] = {0,0}, 254 | [os.date] = {0,2}, 255 | [os.difftime] = {2,2}, 256 | [os.execute] = {0,1}, 257 | [os.exit] = {0,1}, 258 | [os.getenv] = {1,1}, 259 | [os.remove] = {1,1}, 260 | [os.rename] = {2,2}, 261 | [os.setlocale] = {1,2}, 262 | [os.time] = {0,1}, 263 | [os.tmpname] = {0,0}, 264 | [package.loadlib] = {2,2}, 265 | [package.seeall] = {1,1}, 266 | [string.byte] = {1,3}, 267 | [string.char] = {0,math.huge}, 268 | [string.dump] = {1,1}, 269 | [string.find] = {2,4}, 270 | [string.format] = {1,math.huge}, 271 | [string.gmatch] = {2,2}, 272 | [string.gsub] = {3,4}, 273 | [string.len] = {1,1}, 274 | [string.lower] = {1,1}, 275 | [string.match] = {2,3}, 276 | [string.rep] = {2,2}, 277 | [string.reverse] = {1,1}, 278 | [string.sub] = {2,3}, 279 | [string.upper] = {1,1}, 280 | [table.concat] = {1,4}, 281 | [table.insert] = {2,3}, 282 | [table.maxn] = {1,1}, 283 | [table.remove] = {1,2}, 284 | [table.sort] = {1,2}, 285 | [false] = nil -- trick (relies on potentially undefined behavior) 286 | } 287 | 288 | 289 | -- functions with zero or nearly zero side-effects, and with deterministic results, that may be evaluated by the analyzer. 290 | M.safe_function = { 291 | [require] = true, 292 | [rawequal] = true, 293 | [rawget] = true, 294 | [require] = true, -- sort of 295 | [select] = true, 296 | [tonumber] = true, 297 | [tostring] = true, 298 | [type] = true, 299 | [unpack] = true, 300 | [coroutine.create] = true, 301 | -- [coroutine.resume] 302 | [coroutine.running] = true, 303 | [coroutine.status] = true, 304 | [coroutine.wrap] = true, 305 | --[coroutine.yield] 306 | -- [debug.debug] 307 | --[debug.getfenv] = true, 308 | [debug.gethook] = true, 309 | [debug.getinfo] = true, 310 | [debug.getlocal] = true, 311 | [debug.getmetatable] = true, 312 | [debug.getregistry] = true, 313 | [debug.getupvalue] = true, 314 | -- [debug.setfenv] 315 | -- [debug.sethook] 316 | -- [debug.setlocal] 317 | -- [debug.setmetatable] 318 | -- [debug.setupvalue] 319 | -- [debug.traceback] = true, 320 | [io.type] = true, 321 | -- skip all other io.* 322 | [math.abs] = true, 323 | [math.acos] = true, 324 | [math.asin] = true, 325 | [math.atan] = true, 326 | [math.atan2] = true, 327 | [math.ceil] = true, 328 | [math.cos] = true, 329 | [math.cosh] = true, 330 | [math.deg] = true, 331 | [math.exp] = true, 332 | [math.floor] = true, 333 | [math.fmod] = true, 334 | [math.frexp] = true, 335 | [math.ldexp] = true, 336 | [math.log] = true, 337 | [math.log10] = true, 338 | [math.max] = true, 339 | [math.min] = true, 340 | [math.modf] = true, 341 | [math.pow] = true, 342 | [math.rad] = true, 343 | --[math.random] 344 | --[math.randomseed] 345 | [math.sin] = true, 346 | [math.sinh] = true, 347 | [math.sqrt] = true, 348 | [math.tan] = true, 349 | [math.tanh] = true, 350 | [os.clock] = true, -- safe but non-deterministic 351 | [os.date] = true,-- safe but non-deterministic 352 | [os.difftime] = true, 353 | --[os.execute] 354 | --[os.exit] 355 | [os.getenv] = true, -- though depends on environment 356 | --[os.remove] 357 | --[os.rename] 358 | --[os.setlocale] 359 | [os.time] = true, -- safe but non-deterministic 360 | --[os.tmpname] 361 | [string.byte] = true, 362 | [string.char] = true, 363 | [string.dump] = true, 364 | [string.find] = true, 365 | [string.format] = true, 366 | [string.gmatch] = true, 367 | [string.gsub] = true, 368 | [string.len] = true, 369 | [string.lower] = true, 370 | [string.match] = true, 371 | [string.rep] = true, 372 | [string.reverse] = true, 373 | [string.sub] = true, 374 | [string.upper] = true, 375 | [table.maxn] = true, 376 | } 377 | 378 | M.mock_functions = {} 379 | 380 | -- TODO:IMPROVE 381 | local function mockfunction(func, ...) 382 | local inputs = {n=0} 383 | local outputs = {n=0} 384 | local isoutputs 385 | for i=1,select('#', ...) do 386 | local v = select(i, ...) 387 | if type(v) == 'table' then v = v[1] end 388 | if v == 'N' or v == 'I' then v = T.number end 389 | if v == '->' then 390 | isoutputs = true 391 | elseif isoutputs then 392 | outputs[#outputs+1] = v; outputs.n = outputs.n + 1 393 | else 394 | inputs[#inputs+1] = v; inputs.n = inputs.n + 1 395 | end 396 | end 397 | M.mock_functions[func] = {inputs=inputs, outputs=outputs} 398 | end 399 | 400 | 401 | mockfunction(math.abs, 'N', '->', {'N',0,math.huge}) 402 | mockfunction(math.acos, {'N',-1,1}, '->', {'N',0,math.pi/2}) 403 | mockfunction(math.asin, {'N',-1,1}, '->', {'N',-math.pi/2,math.pi/2}) 404 | mockfunction(math.atan, {'N',-math.huge,math.huge}, '->', 405 | {'N',-math.pi/2,math.pi/2}) 406 | --FIX atan2 407 | mockfunction(math.ceil, 'N','->','I') 408 | mockfunction(math.cos, 'N','->',{'N',-1,1}) 409 | mockfunction(math.cosh, 'N','->',{'N',1,math.huge}) 410 | mockfunction(math.deg, 'N','->','N') 411 | mockfunction(math.exp, 'N','->',{'N',0,math.huge}) 412 | mockfunction(math.floor, 'N','->','I') 413 | mockfunction(math.fmod, 'N','N','->','N') 414 | mockfunction(math.frexp, 'N','->',{'N',-1,1},'->','I') 415 | mockfunction(math.ldexp, {'N','I'},'->','N') 416 | mockfunction(math.log, {'N',0,math.huge},'->','N') 417 | mockfunction(math.log10, {'N',0,math.huge},'->','N') 418 | -- function max(...) print 'NOT IMPL'end 419 | -- function min(...) print 'NOT IMPL'end 420 | mockfunction(math.modf, 'N','->','I',{'N',-1,1}) 421 | 422 | mockfunction(math.pow, 'N','N','->','N') -- improve? 423 | mockfunction(math.rad, 'N','->','N') 424 | -- random = function() print 'NOT IMPL' end 425 | mockfunction(math.randomseed, 'N') 426 | mockfunction(math.sin, 'N','->',{'N',-1,1}) 427 | mockfunction(math.sinh, 'N','->','N') 428 | mockfunction(math.sqrt, {'N',0,math.huge},'->',{'N',0,math.huge}) 429 | mockfunction(math.tan, 'N','->','N') -- improve? 430 | mockfunction(math.tanh, 'N','->',{'N',-1,1}) 431 | 432 | 433 | return M 434 | -------------------------------------------------------------------------------- /misc/luainspect/luainspect/luainspect/typecheck.lua: -------------------------------------------------------------------------------- 1 | -- luainspect.typecheck - Type definitions used to check LuaInspect itself. 2 | -- 3 | -- (c) 2010 David Manura, MIT License. 4 | 5 | local T = require "luainspect.types" 6 | 7 | local ast_mt = {__tostring = function(s) return 'AST' end} 8 | 9 | return function(context) 10 | -- AST type. 11 | local ast = T.table { 12 | tag = T.string, 13 | lineinfo=T.table{first=T.table{comments=T.table{T.table{T.string,T.number,T.number}},T.number,T.number,T.number,T.string}, 14 | ast=T.table{comments=T.table{T.table{T.string,T.number,T.number}},T.number,T.number,T.number,T.string}}, 15 | isfield=T.boolean, tag2=T.string, 16 | value=T.universal, valueself=T.number, valuelist=T.table{n=T.number, isvaluepegged=T.boolean}, 17 | resolvedname=T.string, definedglobal=T.boolean, id=T.number, isparam=T.boolean, isset=T.boolean, isused=T.boolean, 18 | isignore=T.boolean, 19 | functionlevel=T.number, localmasked=T.boolean, note=T.string, nocollect=T.table{}, isdead=T.boolean} 20 | -- FIX: some of these are "boolean or nil" actually 21 | ast.localdefinition=ast; ast.localmasking = ast 22 | ast.previous = ast; ast.parent = ast 23 | ast.seevalue = ast; ast.seenote=ast 24 | setmetatable(ast, ast_mt) 25 | 26 | ast[1] = ast; ast[2] = ast 27 | context.apply_value('ast$', ast) 28 | 29 | -- Token type. 30 | context.apply_value('token$', T.table{ 31 | tag=T.string, fpos=T.number, lpos=T.number, keywordid=T.number, ast=ast, [1]=T.string 32 | }) 33 | 34 | -- Lua source code string type. 35 | context.apply_value('src$', '') 36 | 37 | -- SciTE syler object type. 38 | local nf = function()end 39 | context.apply_value('^styler$', T.table{SetState=nf, More=nf, Current=nf, Forward=nf, StartStyling=nf, EndStyling=nf, language=T.string}) 40 | end 41 | -------------------------------------------------------------------------------- /misc/luainspect/luainspect/luainspect/types.lua: -------------------------------------------------------------------------------- 1 | local T = {} -- types 2 | 3 | -- istype[o] iff o represents a type (i.e. set of values) 4 | T.istype = {} 5 | 6 | -- iserror[o] iff o represents an error type (created via T.error). 7 | T.iserror = {} 8 | 9 | -- istabletype[o] iff o represents a table type (created by T.table). 10 | T.istabletype = {} 11 | 12 | -- Number type 13 | T.number = {} 14 | setmetatable(T.number, T.number) 15 | function T.number.__tostring(self) 16 | return 'number' 17 | end 18 | T.istype[T.number] = true 19 | 20 | -- String type 21 | T.string = {} 22 | setmetatable(T.string, T.string) 23 | function T.string.__tostring(self) 24 | return 'string' 25 | end 26 | T.istype[T.string] = true 27 | 28 | -- Boolean type 29 | T.boolean = {} 30 | setmetatable(T.boolean, T.boolean) 31 | function T.boolean.__tostring(self) 32 | return 'boolean' 33 | end 34 | T.istype[T.boolean] = true 35 | 36 | -- Table type 37 | function T.table(t) 38 | T.istype[t] = true 39 | T.istabletype[t] = true 40 | return t 41 | end 42 | 43 | -- Universal type. This is a superset of all other types. 44 | T.universal = {} 45 | setmetatable(T.universal, T.universal) 46 | function T.universal.__tostring(self) 47 | return 'unknown' 48 | end 49 | T.istype[T.universal] = true 50 | 51 | -- nil type. Represents `nil` but can be stored in tables. 52 | T['nil'] = {} 53 | setmetatable(T['nil'], T['nil']) 54 | T['nil'].__tostring = function(self) 55 | return 'nil' 56 | end 57 | T.istype[T['nil']] = true 58 | 59 | -- None type. Represents a non-existent value, in a similar way 60 | -- that `none` is used differently from `nil` in the Lua C API. 61 | T.none = {} 62 | setmetatable(T.none, T.none) 63 | function T.none.__tostring(self) 64 | return 'none' 65 | end 66 | T.istype[T.none] = true 67 | 68 | -- Error type 69 | local CError = {}; CError.__index = CError 70 | function CError.__tostring(self) return "error:" .. tostring(self.value) end 71 | function T.error(val) 72 | local self = setmetatable({value=val}, CError) 73 | T.istype[self] = true 74 | T.iserror[self] = true 75 | return self 76 | end 77 | 78 | 79 | -- Gets a type that is a superset of the two given types. 80 | function T.superset_types(a, b) 81 | if T.iserror[a] then return a end 82 | if T.iserror[b] then return b end 83 | if rawequal(a, b) then -- note: including nil == nil 84 | return a 85 | elseif type(a) == 'string' or a == T.string then 86 | if type(b) == 'string' or b == T.string then 87 | return T.string 88 | else 89 | return T.universal 90 | end 91 | elseif type(a) == 'number' or a == T.number then 92 | if type(b) == 'number' or b == T.number then 93 | return T.number 94 | else 95 | return T.universal 96 | end 97 | elseif type(a) == 'boolean' or a == T.boolean then 98 | if type(b) == 'boolean' or b == T.boolean then 99 | return T.boolean 100 | else 101 | return T.universal 102 | end 103 | else 104 | return T.universal -- IMPROVE 105 | end 106 | end 107 | --[[TESTS: 108 | assert(T.superset_types(2, 2) == 2) 109 | assert(T.superset_types(2, 3) == T.number) 110 | assert(T.superset_types(2, T.number) == T.number) 111 | assert(T.superset_types(T.number, T.string) == T.universal) 112 | print 'DONE' 113 | --]] 114 | 115 | -- Determines whether type `o` certainly evaluates to true (true), 116 | -- certainly evaluates to false (false) or could evaluate to either 117 | -- true of false ('?'). 118 | function T.boolean_cast(o) 119 | if T.iserror[o] then -- special case 120 | return '?' 121 | elseif o == nil or o == false or o == T['nil'] then -- all subsets of {nil, false} 122 | return false 123 | elseif o == T.universal or o == T.boolean then -- all supersets of boolean 124 | return '?' 125 | else -- all subsets of universal - {nil, false} 126 | return true 127 | end 128 | end 129 | 130 | return T 131 | -------------------------------------------------------------------------------- /misc/luainspect/luainspect4vim.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | This module is part of the luainspect.vim plug-in for the Vim text editor. 4 | 5 | Author: Peter Odding 6 | Last Change: July 18, 2013 7 | URL: http://peterodding.com/code/vim/lua-inspect/ 8 | License: MIT 9 | 10 | --]] 11 | 12 | local LI = require 'luainspect.init' 13 | local LA = require 'luainspect.ast' 14 | local LT = require 'luainspect.types' 15 | local MAX_PREVIEW_KEYS = 20 16 | local actions = {} 17 | local myprint 18 | 19 | if type(vim) == 'table' and vim.eval then 20 | -- The Lua interface for Vim redefines print() so it prints inside Vim. 21 | myprint = print 22 | else 23 | -- My $LUA_INIT script redefines print() to enable pretty printing in the 24 | -- interactive prompt, which means strings are printed with surrounding 25 | -- quotes. This would break the communication between Vim and this script. 26 | function myprint(text) io.write(text, '\n') end 27 | end 28 | 29 | local function getcurvar(tokenlist, line, column) -- {{{1 30 | for i, token in ipairs(tokenlist) do 31 | if token.ast.lineinfo then 32 | local l1, c1 = unpack(token.ast.lineinfo.first, 1, 2) 33 | local l2, c2 = unpack(token.ast.lineinfo.last, 1, 2) 34 | if l1 == line and column >= c1 and column <= c2 then 35 | if token.ast.id then return token end 36 | end 37 | end 38 | end 39 | end 40 | 41 | function actions.highlight(tokenlist, line, column, src) -- {{{1 42 | local function dump(token, hlgroup) 43 | local l1, c1 = unpack(token.ast.lineinfo.first, 1, 2) 44 | local l2, c2 = unpack(token.ast.lineinfo.last, 1, 2) 45 | myprint(('%s %i %i %i %i'):format(hlgroup, l1, c1, l2, c2)) 46 | end 47 | -- Print any warnings to show in Vim's quick-fix list. 48 | -- FIXME Why does this report argument count warnings in luainspect/init.lua but not in example.lua?! 49 | local warnings = LI.list_warnings(tokenlist, src) 50 | myprint(#warnings) 51 | for i, warning in ipairs(warnings) do 52 | warning = warning:gsub('%s+', ' ') 53 | myprint(warning) 54 | end 55 | local curvar = getcurvar(tokenlist, line, column) 56 | for i, token in ipairs(tokenlist) do 57 | if curvar and curvar.ast.id == token.ast.id then 58 | dump(token, 'luaInspectSelectedVariable') 59 | end 60 | local ast = token.ast 61 | if ast and (ast.seevalue or ast).note then 62 | local hast = ast.seevalue or ast 63 | if hast.tag == 'Call' then 64 | hast = hast[1] 65 | elseif hast.tag == 'Invoke' then 66 | hast = hast[2] 67 | end 68 | local fpos, lpos = LA.ast_pos_range(hast, tokenlist) 69 | local l1, c1 = LA.pos_to_linecol(fpos, src) 70 | local l2, c2 = LA.pos_to_linecol(lpos, src) 71 | -- TODO: A bit confusing is that LuaInspect seems to emit both zero-based 72 | -- and one-based column numbers (i.e. offsets vs. indices) since the 73 | -- included Metalua lexer was patched to fix a rare bug. 74 | myprint(('luaInspectWrongArgCount %i %i %i %i'):format(l1, c1 - 1, l2, c2 - 1)) 75 | end 76 | if token.tag == 'Id' then 77 | if not token.ast.localdefinition then 78 | dump(token, token.ast.definedglobal and 'luaInspectGlobalDefined' or 'luaInspectGlobalUndefined') 79 | elseif not token.ast.localdefinition.isused then 80 | dump(token, 'luaInspectLocalUnused') 81 | elseif token.ast.localdefinition.functionlevel < token.ast.functionlevel then 82 | dump(token, 'luaInspectUpValue') 83 | elseif token.ast.localdefinition.isset then 84 | dump(token, 'luaInspectLocalMutated') 85 | elseif token.ast.localdefinition.isparam then 86 | dump(token, 'luaInspectParam') 87 | else 88 | dump(token, 'luaInspectLocal') 89 | end 90 | elseif token.ast.isfield then 91 | local a = token.ast 92 | if a.definedglobal or not LT.istype[a.seevalue.value] and a.seevalue.value ~= nil then 93 | dump(token, 'luaInspectFieldDefined') 94 | else 95 | dump(token, 'luaInspectFieldUndefined') 96 | end 97 | end 98 | end 99 | end 100 | 101 | local function previewtable(ast) -- {{{1 102 | -- Print a preview of a table's fields. 103 | local value = (ast.seevalue or ast).value 104 | if type(value) == 'table' then 105 | -- Print at most MAX_PREVIEW_KEYS of the table's keys. 106 | local keys = {} 107 | local count = 0 108 | for k, v in pairs(value) do 109 | if #keys < MAX_PREVIEW_KEYS then 110 | if type(k) == 'string' and k:find '^[A-Za-z_][A-Za-z0-9_]*$' then 111 | keys[#keys+1] = k .. (type(v) == 'function' and '()' or '') 112 | end 113 | end 114 | count = count + 1 115 | end 116 | table.sort(keys) 117 | if count > 0 then 118 | local fields = #keys == 1 and ' field' or ' fields' 119 | local including = count ~= #keys and ' including' or '' 120 | myprint('This table contains ' .. count .. fields .. including .. ':') 121 | for i, k in ipairs(keys) do myprint(' - ' .. k) end 122 | end 123 | end 124 | end 125 | 126 | function actions.tooltip(tokenlist, line, column, src) -- {{{1 127 | for i, token in ipairs(tokenlist) do 128 | local ast = token.ast 129 | if ast.lineinfo then 130 | local l1, c1 = unpack(ast.lineinfo.first, 1, 2) 131 | local l2, c2 = unpack(ast.lineinfo.last, 1, 2) 132 | if l1 == line then 133 | if column >= c1 and column <= c2 and ast.id then 134 | local details = LI.get_value_details(ast, tokenlist, src) 135 | if details ~= '?' then 136 | -- Convert variable type to readable sentence (friendlier to new users IMHO). 137 | details = details:gsub('^[^\n]+', function(vartype) 138 | vartype = vartype:match '^%s*(.-)%s*$' 139 | if vartype:find 'local$' or vartype:find 'global' then 140 | vartype = vartype .. ' ' .. 'variable' 141 | end 142 | local article = details:find '^[aeiou]' and 'an' or 'a' 143 | return "This is " .. article .. ' ' .. vartype .. '.' 144 | end) 145 | myprint(details) 146 | end 147 | previewtable(ast) 148 | break 149 | end 150 | end 151 | end 152 | end 153 | end 154 | 155 | function actions.go_to(tokenlist, line, column) -- {{{1 156 | -- FIXME This only jumps to declaration of local / 1st occurrence of global. 157 | local curvar = getcurvar(tokenlist, line, column) 158 | for i, token in ipairs(tokenlist) do 159 | if curvar and curvar.ast.id == token.ast.id then 160 | local l1, c1 = unpack(token.ast.lineinfo.first, 1, 2) 161 | myprint(l1) 162 | myprint(c1) 163 | break 164 | end 165 | end 166 | end 167 | 168 | function actions.rename(tokenlist, line, column) -- {{{1 169 | local curvar = getcurvar(tokenlist, line, column) 170 | for i, token in ipairs(tokenlist) do 171 | if curvar and curvar.ast.id == token.ast.id then 172 | local l1, c1 = unpack(token.ast.lineinfo.first, 1, 2) 173 | local l2, c2 = unpack(token.ast.lineinfo.last, 1, 2) 174 | if l1 == l2 then myprint(l1 .. ' ' .. c1 .. ' ' .. c2) end 175 | end 176 | end 177 | end 178 | 179 | -- }}} 180 | 181 | return function(src) 182 | local action, file, line, column 183 | action, file, line, column, src = src:match '^(%S+)\n([^\n]*)\n(%d+)\n(%d+)\n(.*)$' 184 | line = tonumber(line) 185 | -- This adjustment was found by trial and error :-| 186 | column = tonumber(column) - 1 187 | src = LA.remove_shebang(src) 188 | -- Quickly parse the source code using loadstring() to check for syntax errors. 189 | local f, err, linenum, colnum, linenum2 = LA.loadstring(src) 190 | if not f then 191 | myprint 'syntax_error' 192 | myprint(linenum) 193 | myprint(colnum) 194 | myprint(linenum2 or 0) 195 | -- Remove prefixed line number from error message because it's redundant. 196 | myprint((err:gsub('^%d+:%s+', ''))) 197 | return 198 | end 199 | -- Now parse the source code using Metalua to build an abstract syntax tree. 200 | local ast = LA.ast_from_string(src, file) 201 | -- This shouldn't happen: Metalua failed to parse what loadstring() accepts! 202 | if not ast then return end 203 | -- Create a list of tokens from the AST and decorate it using LuaInspect. 204 | local tokenlist = LA.ast_to_tokenlist(ast, src) 205 | LI.inspect(ast, tokenlist, src) 206 | -- Branch on the requested action. 207 | if actions[action] then 208 | myprint(action) 209 | actions[action](tokenlist, line, column, src) 210 | end 211 | end 212 | 213 | -- Enable type checking of ast.* expressions. 214 | --! require 'luainspect.typecheck' (context) 215 | 216 | -- vim: ts=2 sw=2 et 217 | -------------------------------------------------------------------------------- /misc/luainspect/metalualib/LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xolox/vim-lua-inspect/0e59882230b7a7af723e9b409b8904819cc68ff1/misc/luainspect/metalualib/LICENSE -------------------------------------------------------------------------------- /misc/luainspect/metalualib/README.TXT: -------------------------------------------------------------------------------- 1 | README.TXT 2 | ========== 3 | For installation matters, cf. INSTALL.TXT 4 | 5 | Metalua 0.5 6 | =========== 7 | 8 | Metalua is a static metaprogramming system for Lua: a set of tools 9 | that let you alter the compilation process in arbitrary, powerful and 10 | maintainable ways. For the potential first-time users of such a 11 | system, a descripition of these tools, as implemented by Metalua, 12 | follows. 13 | 14 | Dynamic Parsers 15 | --------------- 16 | 17 | One of the tools is the dynamic parser, which allows a source file to 18 | change the grammar recognized by the parser, while it's being 19 | parsed. Taken alone, this feature lets you make superficial syntax 20 | tweaks on the language. The parser is based on a parser combinator 21 | library called 'gg'; you should know the half dozen functions in gg 22 | API to do advanced things: 23 | 24 | - There are a couple of very simple combinators like gg.list, 25 | gg.sequence, qq.multisequence, gg.optkeyword etc. that offer a level 26 | of expressiveness comparable to Yacc-like parsers. For instance, if 27 | mlp.expr parses Lua expressions, gg.list{ mlp.expr } creates a 28 | parser which handles lists of Lua expressions. 29 | 30 | - Since you can create all the combinators you can think of (they're 31 | regular, higher-order functions), there also are combinators 32 | specialized for typical language tasks. In Yacc-like systems, the 33 | language definition quickly becomes unreadable, because all 34 | non-native features have to be encoded in clumsy and brittle ways. 35 | So if your parser won't natively let you specify infix operator 36 | precedence and associativity easily, tough luck for you and your 37 | code maintainers. With combinators OTOH, most of such useful 38 | functions already exist, and you can write your owns without 39 | rewriting the parser itself. For instance, adding an infix operator 40 | would just look like: 41 | 42 | > mlp.expr.infix:add{ "xor", prec=40, assoc='left', builder=xor_builder } 43 | 44 | Moreover, combinators tend to produce usable error messages when fed 45 | with syntactically incorrect inputs. It matters, because clearly 46 | explaining why an invalid input is invalid is almost as important as 47 | compiling a valid one, for a use=able compiler. 48 | 49 | Yacc-like systems might seem simpler to adopt than combinators, as 50 | long as they're used on extremely simple problems. However, if you 51 | either try to write something non trivial, or to write a simple macro 52 | in a robust way, you'll need to use lots of messy tricks and hacks, 53 | and spend much more time getting them (approximately) right than 54 | that 1/2 hour required to master the regular features of gg. 55 | 56 | 57 | Real meta-programming 58 | --------------------- 59 | 60 | If you plan to go beyond trivial keyword-for-keyword syntax tweaks, 61 | what will limit you is not syntax definition, but the ability to 62 | manipulate source code conveniently: without the proper tools and 63 | abstractions, even the simplest tasks will turn into a dirty hacks 64 | fest, then either into a maintenance nightmare, or simply into 65 | abandonware. Providing an empowering framework so that you don't get 66 | stuck in such predicaments is Metalua's whole purpose. The central 67 | concept is that programs prefer to manipulate code as trees, whereas 68 | most developers prefer ASCII sources, so both representations must be 69 | freely interchangeable. The make-or-break deal is then: 70 | 71 | - To easily let users see sources as trees, as sources, or as 72 | combination thereof, and switch representations seamlessly. 73 | 74 | - To offer the proper libraries, that won't force you to reinvent a 75 | square wheel, will take care of the most common pitfalls, won't 76 | force you to resort to brittle hacks. 77 | 78 | On the former point, Lisps are at a huge advantage, their user syntax 79 | already being trees. But languages with casual syntax can also offer 80 | interchangeable tree/source views; Metalua has some quoting +{ ... } 81 | and anti-quoting -{ ... } operators which let you switch between both 82 | representations at will: internally it works on trees, but you always 83 | have the option to see them as quoted sources. Metalua also supports a 84 | slightly improved syntax for syntax trees, to improve their 85 | readability. 86 | 87 | Library-wise, Metalua offers a set of syntax tree manipulation tools: 88 | 89 | - Structural pattern matching, a feature traditionally found in 90 | compiler-writing specialized languages (and which has nothing to do 91 | with string regular expressions BTW), which lets you express 92 | advanced tree analysis operations in a compact, readable and 93 | efficient way. If you have to work with advanced data structures 94 | and you try it, you'll never go back. 95 | 96 | - The walker library allows you to perform transformations on big 97 | portions of programs. It lets you easily express things like: 98 | "replace all return statements which aren't in a nested function by 99 | error statements", "rename all local variables and their instances 100 | into unique fresh names", "list the variables which escape this 101 | chunk's scope", "insert a type-checking instruction into every 102 | assignments to variable X", etc. Most of non-trivial macros will 103 | require some of those global code transformations, if you really want 104 | them to behave correctly. 105 | 106 | - Macro hygiene, although not perfect yet in Metalua, is required if 107 | you want to make macro writing reasonably usable (and contrary to a 108 | popular belief, renaming local variables into fresh names only 109 | address the easiest part of the hygiene issue; cf. changelog below 110 | for more details). 111 | 112 | - The existing extensions are progressively refactored in more modular 113 | ways, so that their features can be effectively reused in other 114 | extensions. 115 | 116 | 117 | Noteworthy changes from 0.4.1 to 0.5 118 | ==================================== 119 | 120 | Simplification of the install and structure: 121 | 122 | - This release is included in Lua for Windows, so it now couldn't get simpler 123 | for MS-Windows users! 124 | 125 | - Metalua is written in pure Lua again, thus making it platform-independant. 126 | No more mandatory C libraries. Pluto interface might be back, as an option, 127 | in a future version, but it's not worth the install trouble involved by 128 | DLL dependencies. 129 | 130 | - Simpler build process, just run make.sh or make.bat depending on your OS. 131 | 132 | - Metalua libraries are now in a separate metalua/* package. This allows to 133 | mix them with other Lua libraries, and to use them from plain Lua programs 134 | if you FIXME 135 | 136 | 137 | Other changes: 138 | 139 | - new option -S in metalua: prints sources re-generated from AST, after macro 140 | expansion. 141 | 142 | - compatible with more Lua VMs: 64 bits numbers, integral numbers, big endians... 143 | 144 | - some new extensions: xloop, xmatch, improved match. 145 | 146 | - ASTs now keep track of the source extract that generated them (API is not 147 | mature though, it will be changed and broken). 148 | 149 | - improved table printer: support of a plain-Lua mode, alternative indentation 150 | mode for deeply-nested tables. 151 | 152 | - added a generic table serializer, which handles shared and recursive 153 | sub-tables correctly. 154 | 155 | - gg API has been made slightly more flexible, as a first step towards a 156 | comprehensive syntax support for gg grammar definition. Follow the gg-syntax 157 | branch on github for ongoing work. 158 | 159 | 160 | Noteworthy changes from 0.4 to 0.4.1 161 | ==================================== 162 | 163 | - Proper reporting of runtime errors 164 | - Interactive REPL loop 165 | - Support for 64 bits architectures 166 | - Update to Pluto 2.2 and Lua 5.1.3 167 | - Build for Visual Studio .NET 168 | 169 | Notworthy changes from 0.3 to 0.4 170 | ================================= 171 | 172 | - A significantly bigger code base, mostly due to more libraries: 173 | about 2.5KLoC for libs, 4KLoC for the compiler. However, this remains 174 | tiny in today's desktop computers standards. You don't have to know 175 | all of the system to do useful stuff with it, and since compiled 176 | files are Lua 5.1 compatible, you can keep the "big" system on a 177 | development platform, and keep a lightweight runtime for embedded or 178 | otherwise underpowered targets. 179 | 180 | 181 | - The compiler/interpreter front-end is completely rewritten. The new 182 | frontend program, aptly named 'Metalua', supports proper passing of 183 | arguments to programs, and is generally speaking much more user 184 | friendly than the mlc from the previous version. 185 | 186 | 187 | - Metalua source libraries are looked for in environmemt variable 188 | LUA_MPATH, distinct from LUA_PATH. This way, in an application 189 | that's part Lua part Metalua, you keep a natural access to the 190 | native Lua compiler. 191 | 192 | By convention, Metalua source files should have extension .mlua. By 193 | default, bytecode and plain lua files have higher precedence than 194 | Metalua sources, which lets you easily precompile your libraries. 195 | 196 | 197 | - Compilation of files are separated in different Lua Rings: this 198 | prevents unwanted side-effects when several files are compiled 199 | (This can be turned off, but shouldn't be IMO). 200 | 201 | 202 | - Metalua features are accessible programmatically. Library 203 | 'Metalua.runtime' loads only the libraries necessary to run an 204 | already compiled file; 'Metalua.compile' loads everything useful at 205 | compile-time. 206 | 207 | Transformation functions are available in a library 'mlc' that 208 | contains all meaningful transformation functions in the form 209 | 'mlc.destformat_of_sourceformat()', such as 'mlc.luacfile_of_ast()', 210 | 'mlc.function_of_luastring()' etc. This library has been 211 | significantly completed and rewritten (in Metalua) since v0.3. 212 | 213 | 214 | - Helper libraries have been added. For now they're in the 215 | distribution, at some point they should be luarocked in. These 216 | include: 217 | - Lua Rings and Pluto, duct-taped together into Springs, an improved 218 | Rings that lets states exchange arbitrary data instead of just 219 | scalars and strings. Since Pluto requires a (minor) patch to the 220 | VM, it can be disabled. 221 | - Lua bits for bytecode dumping. 222 | - As always, very large amounts of code borrowed from Yueliang. 223 | - As a commodity, I've also packaged Lua sources in. 224 | 225 | 226 | - Extensions to Lua standard libraries: many more features in table 227 | and the baselib, a couple of string features, and a package system 228 | which correctly handles Metalua source files. 229 | 230 | 231 | - Builds on Linux, OSX, Microsoft Visual Studio. Might build on mingw 232 | (not tested recently, patches welcome). It's easily ported to all 233 | systems with a full support for lua, and if possible dynamic 234 | libraries. 235 | 236 | The MS-windows building is based on a dirty .bat script, because 237 | that's pretty much the only thing you're sure to find on a win32 238 | computer. It uses Microsoft Visual Studio as a compiler (tested with 239 | VC++ 6). 240 | 241 | Notice that parts of the compiler itself are now written in Metalua, 242 | which means that its building now goes through a bootstrapping 243 | stage. 244 | 245 | 246 | - Structural pattern matching improvements: 247 | - now also handles string regular expressions: 'someregexp'/pattern 248 | will match if the tested term is a string accepted by the regexp, 249 | and on success, the list of captures done by the regexp is matched 250 | against pattern. 251 | - Matching of multiple values has been optimized 252 | - the default behavior when no case match is no to raise an error, 253 | it's the most commonly expected case in practice. Trivial to 254 | cancel with a final catch-all pattern. 255 | - generated calls to type() are now hygienic (it's been the cause of 256 | a puzzling bug report; again, hygiene is hard). 257 | 258 | 259 | - AST grammar overhaul: 260 | The whole point of being alpha is to fix APIs with a more relaxed 261 | attitude towards backward compatibility. I think and hope it's the 262 | last AST revision, so here is it: 263 | - `Let{...} is now called `Set{...} 264 | (Functional programmers would expect 'Let' to introduce an 265 | immutable binding, and assignment isn't immutable in Lua) 266 | - `Key{ key, value } in table literals is now written `Pair{ key, value } 267 | (it contained a key *and* its associated value; besides, 'Pair' is 268 | consistent with the name of the for-loop iterator) 269 | - `Method{...} is now `Invoke{...} 270 | (because it's a method invocation, not a method declaration) 271 | - `One{...} is now `Paren{...} and is properly documented 272 | (it's the node representing parentheses: it's necessary, since 273 | parentheses are sometimes meaningful in Lua) 274 | - Operator are simplified: `Op{ 'add', +{2}, +{2} } instead of 275 | `Op{ `Add, +{2}, +{2} }. Operator names match the corresponding 276 | metatable entries, without the leading double-underscore. 277 | - The operators which haven't a metatable counterpart are 278 | deprecated: 'ne', 'ge', 'gt'. 279 | 280 | 281 | - Overhaul of the code walking library: 282 | - the API has been simplified: the fancy predicates proved more 283 | cumbersome to use than a bit of pattern matching in the visitors. 284 | - binding identifiers are handled as a distinct AST class 285 | - walk.id is scope-aware, handles free and bound variables in a 286 | sensible way. 287 | - the currified API proved useless and sometimes cumbersome, it's 288 | been removed. 289 | 290 | 291 | - Hygiene: I originally planned to release a full-featured hygienic 292 | macro system with v0.4, but what exists remains a work in 293 | progress. Lua is a Lisp-1, which means unhygienic macros are very 294 | dangerous, and hygiene a la Scheme pretty much limits macro writing 295 | to a term rewriting subset of the language, which would be crippling 296 | to use. 297 | 298 | Note: inside hygiene, i.e. preventing macro code from capturing 299 | variables in user code, is trivial to address through alpha 300 | conversion, it's not the issue. The trickier part is outside 301 | hygiene, when user's binders capture globals required by the 302 | macro-generated code. That's the cause of pretty puzzling and hard 303 | to find bugs. And the *really* tricky part, which is still an open 304 | problem in Metalua, is when you have several levels of nesting 305 | between user code and macro code. For now this case has to be 306 | hygienized by hand. 307 | 308 | Note 2: Converge has a pretty powerful approach to hygienic macros 309 | in a Lisp-1 language; for reasons that would be too long to expose 310 | here, I don't think its approach would be the best suited to Metalua. 311 | But I might well be proved wrong eventually. 312 | 313 | Note 3: Redittors must have read that Paul Graham has released Arc, 314 | which is also a Lisp-1 with Common Lisp style macros; I expect this 315 | to create a bit of buzz, out of which might emerge proper solutions 316 | the macro hygiene problem. 317 | 318 | 319 | - No more need to create custom syntax for macros when you don't want 320 | to. Extension 'dollar' will let you declare macros in the dollar 321 | table, as in +{block: function dollar.MYMACRO(a, b, c) ... end}, 322 | and use it as $MYMACRO(1, 2, 3) in your code. 323 | 324 | With this extension, you can write macros without knowing anything 325 | about the Metalua parser. Together with quasi-quotes and automatic 326 | hygiene, this will probably be the closest we can go to "macros for 327 | dummies" without creating an unmaintainable mess generator. 328 | 329 | Besides, it's consistent with my official position that focusing on 330 | superficial syntax issues is counter-productive most of the time :) 331 | 332 | 333 | - Lexers can be switched on the fly. This lets you change the set of 334 | keywords temporarily, with the new gg.with_lexer() combinator. You 335 | can also handle radically different syntaxes in a single file (think 336 | multiple-languages systems such as LuaTeX, or programs+goo as PHP). 337 | 338 | 339 | - Incorporation of the bug fixes reported to the mailing list and on 340 | the blog. 341 | 342 | 343 | - New samples and extensions, in various states of completion: 344 | 345 | * lists by comprehension, a la python/haskell. It includes lists 346 | chunking, e.g. mylist[1 ... 3, 5 ... 7] 347 | 348 | * anaphoric macros for 'if' and 'while' statements: with this 349 | extension, the condition of the 'if'/'while' is bound to variable 350 | 'it' in the body; it lets you write things like: 351 | 352 | > while file:read '*l' do print(it) end. 353 | 354 | No runtime overhead when 'it' isn't used in the body. An anaphoric 355 | variable should also be made accessible for functions, to let 356 | easily write anonymous recursive functions. 357 | 358 | * Try ... catch ... finally extension. Syntax is less than ideal, 359 | but the proper way to fix that is to refactor the match extension 360 | to improve code reuse. There would be many other great ways to 361 | leverage a refactored match extension, e.g. destructuring binds or 362 | multiple dispatch methods. To be done in the next version. 363 | 364 | * with ... do extension: it uses try/finally to make sure that 365 | resources will be properly closed. The only constraint on 366 | resources is that they have to support a :close() releasing method. 367 | For instance, he following code guarantees that file1 and file2 368 | will be closed, even if a return or an error occurs in the body. 369 | 370 | > with file1, file2 = io.open "f1.txt", io.open "f2.txt" do 371 | > contents = file1:read'*a' .. file2:read ;*a' 372 | > end 373 | 374 | * continue statement, logging facilities, ternary "?:" choice 375 | operator, assignments as expressions, and a couple of similarly 376 | tiny syntax sugar extensions. 377 | 378 | 379 | You might expect in next versions 380 | ================================= 381 | The next versions of Metalua will provide some of the following 382 | improvements, in no particular order: better error reporting, 383 | especially at runtime (there's a patch I've been too lazy to test 384 | yet), support for 64 bits CPUs, better support for macro hygiene, more 385 | samples and extensions, an adequate test suite, refactored libraries. 386 | 387 | 388 | Credits 389 | ======= 390 | 391 | I'd like to thank the people who wrote the open source code which 392 | makes Metalua run: the Lua team, the authors of Yueliang, Pluto, Lua 393 | Rings, Bitlib; and the people whose bug reports, patches and 394 | insightful discussions dramatically improved the global design, 395 | including John Belmonte, Vyacheslav Egorov, David Manura, Olivier 396 | Gournet, Eric Raible, Laurence Tratt, Alexander Gladysh, Ryan 397 | Pusztai... 398 | -------------------------------------------------------------------------------- /misc/luainspect/metalualib/lexer.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------- 2 | -- Metalua: $Id: mll.lua,v 1.3 2006/11/15 09:07:50 fab13n Exp $ 3 | -- 4 | -- Summary: generic Lua-style lexer definition. You need this plus 5 | -- some keyword additions to create the complete Lua lexer, 6 | -- as is done in mlp_lexer.lua. 7 | -- 8 | -- TODO: 9 | -- 10 | -- * Make it easy to define new flavors of strings. Replacing the 11 | -- lexer.patterns.long_string regexp by an extensible list, with 12 | -- customizable token tag, would probably be enough. Maybe add: 13 | -- + an index of capture for the regexp, that would specify 14 | -- which capture holds the content of the string-like token 15 | -- + a token tag 16 | -- + or a string->string transformer function. 17 | -- 18 | -- * There are some _G.table to prevent a namespace clash which has 19 | -- now disappered. remove them. 20 | ---------------------------------------------------------------------- 21 | -- 22 | -- Copyright (c) 2006, Fabien Fleutot . 23 | -- 24 | -- This software is released under the MIT Licence, see licence.txt 25 | -- for details. 26 | -- 27 | ---------------------------------------------------------------------- 28 | 29 | module ("lexer", package.seeall) 30 | 31 | require 'metalua.runtime' 32 | 33 | 34 | lexer = { alpha={ }, sym={ } } 35 | lexer.__index=lexer 36 | 37 | local debugf = function() end 38 | --local debugf=printf 39 | 40 | ---------------------------------------------------------------------- 41 | -- Patterns used by [lexer:extract] to decompose the raw string into 42 | -- correctly tagged tokens. 43 | ---------------------------------------------------------------------- 44 | lexer.patterns = { 45 | spaces = "^[ \r\n\t]*()", 46 | short_comment = "^%-%-([^\n]*)()\n", 47 | final_short_comment = "^%-%-([^\n]*)()$", 48 | long_comment = "^%-%-%[(=*)%[\n?(.-)%]%1%]()", 49 | long_string = "^%[(=*)%[\n?(.-)%]%1%]()", 50 | number_mantissa = { "^%d+%.?%d*()", "^%d*%.%d+()" }, 51 | number_exponant = "^[eE][%+%-]?%d+()", 52 | number_hex = "^0[xX]%x+()", 53 | word = "^([%a_][%w_]*)()" 54 | } 55 | 56 | ---------------------------------------------------------------------- 57 | -- unescape a whole string, applying [unesc_digits] and 58 | -- [unesc_letter] as many times as required. 59 | ---------------------------------------------------------------------- 60 | local function unescape_string (s) 61 | 62 | -- Turn the digits of an escape sequence into the corresponding 63 | -- character, e.g. [unesc_digits("123") == string.char(123)]. 64 | local function unesc_digits (backslashes, digits) 65 | if #backslashes%2==0 then 66 | -- Even number of backslashes, they escape each other, not the digits. 67 | -- Return them so that unesc_letter() can treaat them 68 | return backslashes..digits 69 | else 70 | -- Remove the odd backslash, which escapes the number sequence. 71 | -- The rest will be returned and parsed by unesc_letter() 72 | backslashes = backslashes :sub (1,-2) 73 | end 74 | local k, j, i = digits:reverse():byte(1, 3) 75 | local z = _G.string.byte "0" 76 | local code = (k or z) + 10*(j or z) + 100*(i or z) - 111*z 77 | if code > 255 then 78 | error ("Illegal escape sequence '\\"..digits.. 79 | "' in string: ASCII codes must be in [0..255]") 80 | end 81 | return backslashes .. string.char (code) 82 | end 83 | 84 | -- Take a letter [x], and returns the character represented by the 85 | -- sequence ['\\'..x], e.g. [unesc_letter "n" == "\n"]. 86 | local function unesc_letter(x) 87 | local t = { 88 | a = "\a", b = "\b", f = "\f", 89 | n = "\n", r = "\r", t = "\t", v = "\v", 90 | ["\\"] = "\\", ["'"] = "'", ['"'] = '"', ["\n"] = "\n" } 91 | return t[x] or error([[Unknown escape sequence '\]]..x..[[']]) 92 | end 93 | 94 | return s 95 | :gsub ("(\\+)([0-9][0-9]?[0-9]?)", unesc_digits) 96 | :gsub ("\\(%D)",unesc_letter) 97 | end 98 | 99 | lexer.extractors = { 100 | "skip_whitespaces_and_comments", 101 | "extract_short_string", "extract_word", "extract_number", 102 | "extract_long_string", "extract_symbol" } 103 | 104 | lexer.token_metatable = { 105 | -- __tostring = function(a) 106 | -- return string.format ("`%s{'%s'}",a.tag, a[1]) 107 | -- end 108 | } 109 | 110 | lexer.lineinfo_metatable = { } 111 | 112 | ---------------------------------------------------------------------- 113 | -- Really extract next token fron the raw string 114 | -- (and update the index). 115 | -- loc: offset of the position just after spaces and comments 116 | -- previous_i: offset in src before extraction began 117 | ---------------------------------------------------------------------- 118 | function lexer:extract () 119 | local previous_i = self.i 120 | local loc = self.i 121 | local eof, token 122 | 123 | -- Put line info, comments and metatable around the tag and content 124 | -- provided by extractors, thus returning a complete lexer token. 125 | -- first_line: line # at the beginning of token 126 | -- first_column_offset: char # of the last '\n' before beginning of token 127 | -- i: scans from beginning of prefix spaces/comments to end of token. 128 | local function build_token (tag, content) 129 | assert (tag and content) 130 | local i, first_line, first_column_offset, previous_line_length = 131 | previous_i, self.line, self.column_offset, nil 132 | 133 | -- update self.line and first_line. i := indexes of '\n' chars 134 | while true do 135 | i = self.src:match ("\n()", i, true) 136 | --PATCHED:LuaInspect: above line was not counting line numbers 137 | -- correctly when first character of file was a \n. 138 | if not i or i>self.i then break end -- no more '\n' until end of token 139 | previous_line_length = i - self.column_offset 140 | if loc and i <= loc then -- '\n' before beginning of token 141 | first_column_offset = i 142 | first_line = first_line+1 143 | end 144 | self.line = self.line+1 145 | self.column_offset = i 146 | end 147 | 148 | -- lineinfo entries: [1]=line, [2]=column, [3]=char, [4]=filename 149 | local fli = { first_line, loc-first_column_offset, loc, self.src_name } 150 | local lli = { self.line, self.i-self.column_offset-1, self.i-1, self.src_name } 151 | --Pluto barfes when the metatable is set:( 152 | setmetatable(fli, lexer.lineinfo_metatable) 153 | setmetatable(lli, lexer.lineinfo_metatable) 154 | local a = { tag = tag, lineinfo = { first=fli, last=lli }, content } 155 | if lli[2]==-1 then lli[1], lli[2] = lli[1]-1, previous_line_length-1 end 156 | if #self.attached_comments > 0 then 157 | a.lineinfo.comments = self.attached_comments 158 | fli.comments = self.attached_comments 159 | if self.lineinfo_last then 160 | self.lineinfo_last.comments = self.attached_comments 161 | end 162 | end 163 | self.attached_comments = { } 164 | return setmetatable (a, self.token_metatable) 165 | end -- 166 | 167 | for ext_idx, extractor in ipairs(self.extractors) do 168 | -- printf("method = %s", method) 169 | local tag, content = self [extractor] (self) 170 | -- [loc] is placed just after the leading whitespaces and comments; 171 | -- for this to work, the whitespace extractor *must be* at index 1. 172 | if ext_idx==1 then loc = self.i end 173 | 174 | if tag then 175 | --printf("`%s{ %q }\t%i", tag, content, loc); 176 | return build_token (tag, content) 177 | end 178 | end 179 | 180 | error "None of the lexer extractors returned anything!" 181 | end 182 | 183 | ---------------------------------------------------------------------- 184 | -- skip whites and comments 185 | -- FIXME: doesn't take into account: 186 | -- - unterminated long comments 187 | -- - short comments at last line without a final \n 188 | ---------------------------------------------------------------------- 189 | function lexer:skip_whitespaces_and_comments() 190 | local table_insert = _G.table.insert 191 | repeat -- loop as long as a space or comment chunk is found 192 | local _, j 193 | local again = false 194 | local last_comment_content = nil 195 | -- skip spaces 196 | self.i = self.src:match (self.patterns.spaces, self.i) 197 | -- skip a long comment if any 198 | _, last_comment_content, j = 199 | self.src :match (self.patterns.long_comment, self.i) 200 | if j then 201 | table_insert(self.attached_comments, 202 | {last_comment_content, self.i, j, "long"}) 203 | self.i=j; again=true 204 | end 205 | -- skip a short comment if any 206 | last_comment_content, j = self.src:match (self.patterns.short_comment, self.i) 207 | if j then 208 | table_insert(self.attached_comments, 209 | {last_comment_content, self.i, j, "short"}) 210 | self.i=j; again=true 211 | end 212 | if self.i>#self.src then return "Eof", "eof" end 213 | until not again 214 | 215 | if self.src:match (self.patterns.final_short_comment, self.i) then 216 | return "Eof", "eof" end 217 | --assert (not self.src:match(self.patterns.short_comment, self.i)) 218 | --assert (not self.src:match(self.patterns.long_comment, self.i)) 219 | -- --assert (not self.src:match(self.patterns.spaces, self.i)) 220 | return 221 | end 222 | 223 | ---------------------------------------------------------------------- 224 | -- extract a '...' or "..." short string 225 | ---------------------------------------------------------------------- 226 | function lexer:extract_short_string() 227 | -- [k] is the first unread char, [self.i] points to [k] in [self.src] 228 | local j, k = self.i, self.src :sub (self.i,self.i) 229 | if k~="'" and k~='"' then return end 230 | local i = self.i + 1 231 | local j = i 232 | while true do 233 | -- k = opening char: either simple-quote or double-quote 234 | -- i = index of beginning-of-string 235 | -- x = next "interesting" character 236 | -- j = position after interesting char 237 | -- y = char just after x 238 | local x, y 239 | x, j, y = self.src :match ("([\\\r\n"..k.."])()(.?)", j) 240 | if x == '\\' then j=j+1 -- don't parse escaped char 241 | elseif x == k then break -- unescaped end of string 242 | else -- eof or '\r' or '\n' reached before end of string 243 | assert (not x or x=="\r" or x=="\n") 244 | error "Unterminated string" 245 | end 246 | end 247 | self.i = j 248 | 249 | return "String", unescape_string (self.src:sub (i,j-2)) 250 | end 251 | 252 | ---------------------------------------------------------------------- 253 | -- 254 | ---------------------------------------------------------------------- 255 | function lexer:extract_word() 256 | -- Id / keyword 257 | local word, j = self.src:match (self.patterns.word, self.i) 258 | if word then 259 | self.i = j 260 | if self.alpha [word] then return "Keyword", word 261 | else return "Id", word end 262 | end 263 | end 264 | 265 | ---------------------------------------------------------------------- 266 | -- 267 | ---------------------------------------------------------------------- 268 | function lexer:extract_number() 269 | -- Number 270 | local j = self.src:match(self.patterns.number_hex, self.i) 271 | if not j then 272 | j = self.src:match (self.patterns.number_mantissa[1], self.i) or 273 | self.src:match (self.patterns.number_mantissa[2], self.i) 274 | if j then 275 | j = self.src:match (self.patterns.number_exponant, j) or j; 276 | end 277 | end 278 | if not j then return end 279 | -- Number found, interpret with tonumber() and return it 280 | local n = tonumber (self.src:sub (self.i, j-1)) 281 | self.i = j 282 | return "Number", n 283 | end 284 | 285 | ---------------------------------------------------------------------- 286 | -- 287 | ---------------------------------------------------------------------- 288 | function lexer:extract_long_string() 289 | -- Long string 290 | local _, content, j = self.src:match (self.patterns.long_string, self.i) 291 | if j then self.i = j; return "String", content end 292 | end 293 | 294 | ---------------------------------------------------------------------- 295 | -- 296 | ---------------------------------------------------------------------- 297 | function lexer:extract_symbol() 298 | -- compound symbol 299 | local k = self.src:sub (self.i,self.i) 300 | local symk = self.sym [k] 301 | if not symk then 302 | self.i = self.i + 1 303 | return "Keyword", k 304 | end 305 | for _, sym in pairs (symk) do 306 | if sym == self.src:sub (self.i, self.i + #sym - 1) then 307 | self.i = self.i + #sym; 308 | return "Keyword", sym 309 | end 310 | end 311 | -- single char symbol 312 | self.i = self.i+1 313 | return "Keyword", k 314 | end 315 | 316 | ---------------------------------------------------------------------- 317 | -- Add a keyword to the list of keywords recognized by the lexer. 318 | ---------------------------------------------------------------------- 319 | function lexer:add (w, ...) 320 | assert(not ..., "lexer:add() takes only one arg, although possibly a table") 321 | if type (w) == "table" then 322 | for _, x in ipairs (w) do self:add (x) end 323 | else 324 | if w:match (self.patterns.word .. "$") then self.alpha [w] = true 325 | elseif w:match "^%p%p+$" then 326 | local k = w:sub(1,1) 327 | local list = self.sym [k] 328 | if not list then list = { }; self.sym [k] = list end 329 | _G.table.insert (list, w) 330 | elseif w:match "^%p$" then return 331 | else error "Invalid keyword" end 332 | end 333 | end 334 | 335 | ---------------------------------------------------------------------- 336 | -- Return the [n]th next token, without consumming it. 337 | -- [n] defaults to 1. If it goes pass the end of the stream, an EOF 338 | -- token is returned. 339 | ---------------------------------------------------------------------- 340 | function lexer:peek (n) 341 | if not n then n=1 end 342 | if n > #self.peeked then 343 | for i = #self.peeked+1, n do 344 | self.peeked [i] = self:extract() 345 | end 346 | end 347 | return self.peeked [n] 348 | end 349 | 350 | ---------------------------------------------------------------------- 351 | -- Return the [n]th next token, removing it as well as the 0..n-1 352 | -- previous tokens. [n] defaults to 1. If it goes pass the end of the 353 | -- stream, nil is returned. 354 | ---------------------------------------------------------------------- 355 | function lexer:next (n) 356 | n = n or 1 357 | self:peek (n) 358 | local a 359 | for i=1,n do 360 | a = _G.table.remove (self.peeked, 1) 361 | if a then 362 | --debugf ("lexer:next() ==> %s %s", 363 | -- table.tostring(a), tostring(a)) 364 | end 365 | self.lastline = a.lineinfo.last[1] 366 | end 367 | self.lineinfo_last = a.lineinfo.last 368 | return a 369 | --PATCHED:LuaInspect: eof_token was undefined (nil). 370 | end 371 | 372 | ---------------------------------------------------------------------- 373 | -- Returns an object which saves the stream's current state. 374 | ---------------------------------------------------------------------- 375 | -- FIXME there are more fields than that to save 376 | function lexer:save () return { self.i; _G.table.cat(self.peeked) } end 377 | 378 | ---------------------------------------------------------------------- 379 | -- Restore the stream's state, as saved by method [save]. 380 | ---------------------------------------------------------------------- 381 | -- FIXME there are more fields than that to restore 382 | function lexer:restore (s) self.i=s[1]; self.peeked=s[2] end 383 | 384 | ---------------------------------------------------------------------- 385 | -- Resynchronize: cancel any token in self.peeked, by emptying the 386 | -- list and resetting the indexes 387 | ---------------------------------------------------------------------- 388 | function lexer:sync() 389 | local p1 = self.peeked[1] 390 | if p1 then 391 | li = p1.lineinfo.first 392 | self.line, self.i = li[1], li[3] 393 | self.column_offset = self.i - li[2] 394 | self.peeked = { } 395 | self.attached_comments = p1.lineinfo.first.comments or { } 396 | end 397 | end 398 | 399 | ---------------------------------------------------------------------- 400 | -- Take the source and offset of an old lexer. 401 | ---------------------------------------------------------------------- 402 | function lexer:takeover(old) 403 | self:sync() 404 | self.line, self.column_offset, self.i, self.src, self.attached_comments = 405 | old.line, old.column_offset, old.i, old.src, old.attached_comments 406 | return self 407 | end 408 | 409 | -- function lexer:lineinfo() 410 | -- if self.peeked[1] then return self.peeked[1].lineinfo.first 411 | -- else return { self.line, self.i-self.column_offset, self.i } end 412 | -- end 413 | 414 | 415 | ---------------------------------------------------------------------- 416 | -- Return the current position in the sources. This position is between 417 | -- two tokens, and can be within a space / comment area, and therefore 418 | -- have a non-null width. :lineinfo_left() returns the beginning of the 419 | -- separation area, :lineinfo_right() returns the end of that area. 420 | -- 421 | -- ____ last consummed token ____ first unconsummed token 422 | -- / / 423 | -- XXXXX YYYYY 424 | -- \____ \____ 425 | -- :lineinfo_left() :lineinfo_right() 426 | ---------------------------------------------------------------------- 427 | function lexer:lineinfo_right() 428 | return self:peek(1).lineinfo.first 429 | end 430 | 431 | function lexer:lineinfo_left() 432 | return self.lineinfo_last 433 | end 434 | 435 | ---------------------------------------------------------------------- 436 | -- Create a new lexstream. 437 | ---------------------------------------------------------------------- 438 | function lexer:newstream (src_or_stream, name) 439 | name = name or "?" 440 | if type(src_or_stream)=='table' then -- it's a stream 441 | return setmetatable ({ }, self) :takeover (src_or_stream) 442 | elseif type(src_or_stream)=='string' then -- it's a source string 443 | local src = src_or_stream 444 | local stream = { 445 | src_name = name; -- Name of the file 446 | src = src; -- The source, as a single string 447 | peeked = { }; -- Already peeked, but not discarded yet, tokens 448 | i = 1; -- Character offset in src 449 | line = 1; -- Current line number 450 | column_offset = 0; -- distance from beginning of file to last '\n' 451 | attached_comments = { },-- comments accumulator 452 | lineinfo_last = { 1, 1, 1, name } 453 | } 454 | setmetatable (stream, self) 455 | 456 | -- skip initial sharp-bang for unix scripts 457 | -- FIXME: redundant with mlp.chunk() 458 | if src and src :match "^#" then stream.i = src :find "\n" + 1 end 459 | return stream 460 | else 461 | assert(false, ":newstream() takes a source string or a stream, not a ".. 462 | type(src_or_stream)) 463 | end 464 | end 465 | 466 | ---------------------------------------------------------------------- 467 | -- if there's no ... args, return the token a (whose truth value is 468 | -- true) if it's a `Keyword{ }, or nil. If there are ... args, they 469 | -- have to be strings. if the token a is a keyword, and it's content 470 | -- is one of the ... args, then returns it (it's truth value is 471 | -- true). If no a keyword or not in ..., return nil. 472 | ---------------------------------------------------------------------- 473 | function lexer:is_keyword (a, ...) 474 | if not a or a.tag ~= "Keyword" then return false end 475 | local words = {...} 476 | if #words == 0 then return a[1] end 477 | for _, w in ipairs (words) do 478 | if w == a[1] then return w end 479 | end 480 | return false 481 | end 482 | 483 | ---------------------------------------------------------------------- 484 | -- Cause an error if the next token isn't a keyword whose content 485 | -- is listed among ... args (which have to be strings). 486 | ---------------------------------------------------------------------- 487 | function lexer:check (...) 488 | local words = {...} 489 | local a = self:next() 490 | local function err () 491 | error ("Got " .. tostring (a) .. 492 | ", expected one of these keywords : '" .. 493 | _G.table.concat (words,"', '") .. "'") end 494 | 495 | if not a or a.tag ~= "Keyword" then err () end 496 | if #words == 0 then return a[1] end 497 | for _, w in ipairs (words) do 498 | if w == a[1] then return w end 499 | end 500 | err () 501 | end 502 | 503 | ---------------------------------------------------------------------- 504 | -- 505 | ---------------------------------------------------------------------- 506 | function lexer:clone() 507 | local clone = { 508 | alpha = table.deep_copy(self.alpha), 509 | sym = table.deep_copy(self.sym) } 510 | setmetatable(clone, self) 511 | clone.__index = clone 512 | return clone 513 | end 514 | -------------------------------------------------------------------------------- /misc/luainspect/metalualib/metalua/base.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------- 2 | ---------------------------------------------------------------------- 3 | -- 4 | -- Base library extension 5 | -- 6 | ---------------------------------------------------------------------- 7 | ---------------------------------------------------------------------- 8 | 9 | if not metalua then metalua = {} end --PATCHED.. rawset(getfenv(), 'metalua', { }) end 10 | metalua.version = "v-0.5" 11 | 12 | if not rawpairs then 13 | rawpairs, rawipairs, rawtype = pairs, ipairs, type 14 | end 15 | 16 | function pairsmt(x) -- PATCHED:LuaInspect [*] 17 | assert(type(x)=='table', 'pairs() expects a table') 18 | local mt = getmetatable(x) 19 | if mt then 20 | local mtp = mt.__pairs 21 | if mtp then return mtp(x) end 22 | end 23 | return rawpairs(x) 24 | end 25 | 26 | function ipairsmt(x) --PATCHED:LuaInspect [*] 27 | assert(type(x)=='table', 'ipairs() expects a table') 28 | local mt = getmetatable(x) 29 | if mt then 30 | local mti = mt.__ipairs 31 | if mti then return mti(x) end 32 | end 33 | return rawipairs(x) 34 | end 35 | --PATCHED:LuaInspect: [*] For performance, compatibility, 36 | -- and debugging reasons, avoid overriding builtins. 37 | 38 | 39 | --[[ 40 | function type(x) 41 | local mt = getmetatable(x) 42 | if mt then 43 | local mtt = mt.__type 44 | if mtt then return mtt end 45 | end 46 | return rawtype(x) 47 | end 48 | ]] 49 | 50 | function min (a, ...) 51 | for n in values{...} do if na then a=n end end 57 | return a 58 | end 59 | 60 | function o (...) 61 | local args = {...} 62 | local function g (...) 63 | local result = {...} 64 | for i=#args, 1, -1 do result = {args[i](unpack(result))} end 65 | return unpack (result) 66 | end 67 | return g 68 | end 69 | 70 | function id (...) return ... end 71 | function const (k) return function () return k end end 72 | 73 | function printf(...) return print(string.format(...)) end 74 | function eprintf(...) 75 | io.stderr:write(string.format(...).."\n") 76 | end 77 | 78 | function ivalues (x) 79 | assert(type(x)=='table', 'ivalues() expects a table') 80 | local i = 1 81 | local function iterator () 82 | local r = x[i]; i=i+1; return r 83 | end 84 | return iterator 85 | end 86 | 87 | 88 | function values (x) 89 | assert(type(x)=='table', 'values() expects a table') 90 | local function iterator (state) 91 | local it 92 | state.content, it = next(state.list, state.content) 93 | return it 94 | end 95 | return iterator, { list = x } 96 | end 97 | 98 | function keys (x) 99 | assert(type(x)=='table', 'keys() expects a table') 100 | local function iterator (state) 101 | local it = next(state.list, state.content) 102 | state.content = it 103 | return it 104 | end 105 | return iterator, { list = x } 106 | end 107 | 108 | -------------------------------------------------------------------------------- /misc/luainspect/metalualib/metalua/runtime.lua: -------------------------------------------------------------------------------- 1 | require 'metalua.base' 2 | require 'metalua.table2' 3 | require 'metalua.string2' 4 | -------------------------------------------------------------------------------- /misc/luainspect/metalualib/metalua/string2.lua: -------------------------------------------------------------------------------- 1 | 2 | ---------------------------------------------------------------------- 3 | ---------------------------------------------------------------------- 4 | -- 5 | -- String module extension 6 | -- 7 | ---------------------------------------------------------------------- 8 | ---------------------------------------------------------------------- 9 | 10 | -- Courtesy of lua-users.org 11 | function string.split(str, pat) 12 | local t = {} 13 | local fpat = "(.-)" .. pat 14 | local last_end = 1 15 | local s, e, cap = string.find(str, fpat, 1) 16 | while s do 17 | if s ~= 1 or cap ~= "" then 18 | table.insert(t,cap) 19 | end 20 | last_end = e+1 21 | s, e, cap = string.find(str, fpat, last_end) 22 | end 23 | if last_end <= string.len(str) then 24 | cap = string.sub(str, last_end) 25 | table.insert(t, cap) 26 | end 27 | return t 28 | end 29 | 30 | -- "match" is regularly used as a keyword for pattern matching, 31 | -- so here is an always available substitute. 32 | string.strmatch = string["match"] 33 | 34 | -- change a compiled string into a function 35 | function string.undump(str) 36 | if str:strmatch '^\027LuaQ' or str:strmatch '^#![^\n]+\n\027LuaQ' then 37 | local f = (lua_loadstring or loadstring)(str) 38 | return f 39 | else 40 | error "Not a chunk dump" 41 | end 42 | end 43 | 44 | return string -------------------------------------------------------------------------------- /misc/luainspect/metalualib/metalua/table2.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------- 2 | ---------------------------------------------------------------------- 3 | -- 4 | -- Table module extension 5 | -- 6 | ---------------------------------------------------------------------- 7 | ---------------------------------------------------------------------- 8 | 9 | -- todo: table.scan (scan1?) fold1? flip? 10 | 11 | function table.transpose(t) 12 | local tt = { } 13 | for a, b in pairs(t) do tt[b] = a end 14 | return tt 15 | end 16 | 17 | function table.iforeach(f, ...) 18 | -- assert (type (f) == "function") [wouldn't allow metamethod __call] 19 | local nargs = select("#", ...) 20 | if nargs==1 then -- Quick iforeach (most common case), just one table arg 21 | local t = ... 22 | assert (type (t) == "table") 23 | for i = 1, #t do 24 | local result = f (t[i]) 25 | -- If the function returns non-false, stop iteration 26 | if result then return result end 27 | end 28 | else -- advanced case: boundaries and/or multiple tables 29 | -- 1 - find boundaries if any 30 | local args, fargs, first, last, arg1 = {...}, { } 31 | if type(args[1]) ~= "number" then first, arg1 = 1, 1 32 | elseif type(args[2]) ~= "number" then first, last, arg1 = 1, args[1], 2 33 | else first, last, i = args[1], args[2], 3 end 34 | assert (nargs > arg1) 35 | -- 2 - determine upper boundary if not given 36 | if not last then for i = arg1, nargs do 37 | assert (type (args[i]) == "table") 38 | last = max (#args[i], last) 39 | end end 40 | -- 3 - perform the iteration 41 | for i = first, last do 42 | for j = arg1, nargs do fargs[j] = args[j][i] end -- build args list 43 | local result = f (unpack (fargs)) -- here is the call 44 | -- If the function returns non-false, stop iteration 45 | if result then return result end 46 | end 47 | end 48 | end 49 | 50 | function table.imap (f, ...) 51 | local result, idx = { }, 1 52 | local function g(...) result[idx] = f(...); idx=idx+1 end 53 | table.iforeach(g, ...) 54 | return result 55 | end 56 | 57 | function table.ifold (f, acc, ...) 58 | local function g(...) acc = f (acc,...) end 59 | table.iforeach (g, ...) 60 | return acc 61 | end 62 | 63 | -- function table.ifold1 (f, ...) 64 | -- return table.ifold (f, acc, 2, false, ...) 65 | -- end 66 | 67 | function table.izip(...) 68 | local function g(...) return {...} end 69 | return table.imap(g, ...) 70 | end 71 | 72 | function table.ifilter(f, t) 73 | local yes, no = { }, { } 74 | for i=1,#t do table.insert (f(t[i]) and yes or no, t[i]) end 75 | return yes, no 76 | end 77 | 78 | function table.icat(...) 79 | local result = { } 80 | for t in values {...} do 81 | for x in values (t) do 82 | table.insert (result, x) 83 | end 84 | end 85 | return result 86 | end 87 | 88 | function table.iflatten (x) return table.icat (unpack (x)) end 89 | 90 | function table.irev (t) 91 | local result, nt = { }, #t 92 | for i=0, nt-1 do result[nt-i] = t[i+1] end 93 | return result 94 | end 95 | 96 | function table.isub (t, ...) 97 | local ti, u = table.insert, { } 98 | local args, nargs = {...}, select("#", ...) 99 | for i=1, nargs/2 do 100 | local a, b = args[2*i-1], args[2*i] 101 | for i=a, b, a<=b and 1 or -1 do ti(u, t[i]) end 102 | end 103 | return u 104 | end 105 | 106 | function table.iall (f, ...) 107 | local result = true 108 | local function g(...) return not f(...) end 109 | return not table.iforeach(g, ...) 110 | --return result 111 | end 112 | 113 | function table.iany (f, ...) 114 | local function g(...) return not f(...) end 115 | return not table.iall(g, ...) 116 | end 117 | 118 | function table.shallow_copy(x) 119 | local y={ } 120 | for k, v in pairs(x) do y[k]=v end 121 | return y 122 | end 123 | 124 | -- Warning, this is implementation dependent: it relies on 125 | -- the fact the [next()] enumerates the array-part before the hash-part. 126 | function table.cat(...) 127 | local y={ } 128 | for x in values{...} do 129 | -- cat array-part 130 | for _, v in ipairs(x) do table.insert(y,v) end 131 | -- cat hash-part 132 | local lx, k = #x 133 | if lx>0 then k=next(x,lx) else k=next(x) end 134 | while k do y[k]=x[k]; k=next(x,k) end 135 | end 136 | return y 137 | end 138 | 139 | function table.deep_copy(x) 140 | local tracker = { } 141 | local function aux (x) 142 | if type(x) == "table" then 143 | local y=tracker[x] 144 | if y then return y end 145 | y = { }; tracker[x] = y 146 | setmetatable (y, getmetatable (x)) 147 | for k,v in pairs(x) do y[aux(k)] = aux(v) end 148 | return y 149 | else return x end 150 | end 151 | return aux(x) 152 | end 153 | 154 | function table.override(dst, src) 155 | for k, v in pairs(src) do dst[k] = v end 156 | for i = #src+1, #dst do dst[i] = nil end 157 | return dst 158 | end 159 | 160 | 161 | function table.range(a,b,c) 162 | if not b then assert(not(c)); b=a; a=1 163 | elseif not c then c = (b>=a) and 1 or -1 end 164 | local result = { } 165 | for i=a, b, c do table.insert(result, i) end 166 | return result 167 | end 168 | 169 | -- FIXME: new_indent seems to be always nil?! 170 | -- FIXME: accumulator function should be configurable, 171 | -- so that print() doesn't need to bufferize the whole string 172 | -- before starting to print. 173 | function table.tostring(t, ...) 174 | local PRINT_HASH, HANDLE_TAG, FIX_INDENT, LINE_MAX, INITIAL_INDENT = true, true 175 | for _, x in ipairs {...} do 176 | if type(x) == "number" then 177 | if not LINE_MAX then LINE_MAX = x 178 | else INITIAL_INDENT = x end 179 | elseif x=="nohash" then PRINT_HASH = false 180 | elseif x=="notag" then HANDLE_TAG = false 181 | else 182 | local n = string['match'](x, "^indent%s*(%d*)$") 183 | if n then FIX_INDENT = tonumber(n) or 3 end 184 | end 185 | end 186 | LINE_MAX = LINE_MAX or math.huge 187 | INITIAL_INDENT = INITIAL_INDENT or 1 188 | 189 | local current_offset = 0 -- indentation level 190 | local xlen_cache = { } -- cached results for xlen() 191 | local acc_list = { } -- Generated bits of string 192 | local function acc(...) -- Accumulate a bit of string 193 | local x = table.concat{...} 194 | current_offset = current_offset + #x 195 | table.insert(acc_list, x) 196 | end 197 | local function valid_id(x) 198 | -- FIXME: we should also reject keywords; but the list of 199 | -- current keywords is not fixed in metalua... 200 | return type(x) == "string" 201 | and string['match'](x, "^[a-zA-Z_][a-zA-Z0-9_]*$") 202 | end 203 | 204 | -- Compute the number of chars it would require to display the table 205 | -- on a single line. Helps to decide whether some carriage returns are 206 | -- required. Since the size of each sub-table is required many times, 207 | -- it's cached in [xlen_cache]. 208 | local xlen_type = { } 209 | local function xlen(x, nested) 210 | nested = nested or { } 211 | if x==nil then return #"nil" end 212 | --if nested[x] then return #tostring(x) end -- already done in table 213 | local len = xlen_cache[x] 214 | if len then return len end 215 | local f = xlen_type[type(x)] 216 | if not f then return #tostring(x) end 217 | len = f (x, nested) 218 | xlen_cache[x] = len 219 | return len 220 | end 221 | 222 | -- optim: no need to compute lengths if I'm not going to use them 223 | -- anyway. 224 | if LINE_MAX == math.huge then xlen = function() return 0 end end 225 | 226 | xlen_type["nil"] = function () return 3 end 227 | function xlen_type.number (x) return #tostring(x) end 228 | function xlen_type.boolean (x) return x and 4 or 5 end 229 | function xlen_type.string (x) return #string.format("%q",x) end 230 | function xlen_type.table (adt, nested) 231 | 232 | -- Circular references detection 233 | if nested [adt] then return #tostring(adt) end 234 | nested [adt] = true 235 | 236 | local has_tag = HANDLE_TAG and valid_id(adt.tag) 237 | local alen = #adt 238 | local has_arr = alen>0 239 | local has_hash = false 240 | local x = 0 241 | 242 | if PRINT_HASH then 243 | -- first pass: count hash-part 244 | for k, v in pairs(adt) do 245 | if k=="tag" and has_tag then 246 | -- this is the tag -> do nothing! 247 | elseif type(k)=="number" and k<=alen and math.fmod(k,1)==0 then 248 | -- array-part pair -> do nothing! 249 | else 250 | has_hash = true 251 | if valid_id(k) then x=x+#k 252 | else x = x + xlen (k, nested) + 2 end -- count surrounding brackets 253 | x = x + xlen (v, nested) + 5 -- count " = " and ", " 254 | end 255 | end 256 | end 257 | 258 | for i = 1, alen do x = x + xlen (adt[i], nested) + 2 end -- count ", " 259 | 260 | nested[adt] = false -- No more nested calls 261 | 262 | if not (has_tag or has_arr or has_hash) then return 3 end 263 | if has_tag then x=x+#adt.tag+1 end 264 | if not (has_arr or has_hash) then return x end 265 | if not has_hash and alen==1 and type(adt[1])~="table" then 266 | return x-2 -- substract extraneous ", " 267 | end 268 | return x+2 -- count "{ " and " }", substract extraneous ", " 269 | end 270 | 271 | -- Recursively print a (sub) table at given indentation level. 272 | -- [newline] indicates whether newlines should be inserted. 273 | local function rec (adt, nested, indent) 274 | if not FIX_INDENT then indent = current_offset end 275 | local function acc_newline() 276 | acc ("\n"); acc (string.rep (" ", indent)) 277 | current_offset = indent 278 | end 279 | local x = { } 280 | x["nil"] = function() acc "nil" end 281 | function x.number() acc (tostring (adt)) end 282 | --function x.string() acc (string.format ("%q", adt)) end 283 | function x.string() acc ((string.format ("%q", adt):gsub("\\\n", "\\n"))) end 284 | function x.boolean() acc (adt and "true" or "false") end 285 | function x.table() 286 | if nested[adt] then acc(tostring(adt)); return end 287 | nested[adt] = true 288 | 289 | 290 | local has_tag = HANDLE_TAG and valid_id(adt.tag) 291 | local alen = #adt 292 | local has_arr = alen>0 293 | local has_hash = false 294 | 295 | if has_tag then acc("`"); acc(adt.tag) end 296 | 297 | -- First pass: handle hash-part 298 | if PRINT_HASH then 299 | for k, v in pairs(adt) do 300 | -- pass if the key belongs to the array-part or is the "tag" field 301 | if not (k=="tag" and HANDLE_TAG) and 302 | not (type(k)=="number" and k<=alen and math.fmod(k,1)==0) then 303 | 304 | -- Is it the first time we parse a hash pair? 305 | if not has_hash then 306 | acc "{ " 307 | if not FIX_INDENT then indent = current_offset end 308 | else acc ", " end 309 | 310 | -- Determine whether a newline is required 311 | local is_id, expected_len = valid_id(k) 312 | if is_id then expected_len = #k + xlen (v, nested) + #" = , " 313 | else expected_len = xlen (k, nested) + 314 | xlen (v, nested) + #"[] = , " end 315 | if has_hash and expected_len + current_offset > LINE_MAX 316 | then acc_newline() end 317 | 318 | -- Print the key 319 | if is_id then acc(k); acc " = " 320 | else acc "["; rec (k, nested, indent+(FIX_INDENT or 0)); acc "] = " end 321 | 322 | -- Print the value 323 | rec (v, nested, indent+(FIX_INDENT or 0)) 324 | has_hash = true 325 | end 326 | end 327 | end 328 | 329 | -- Now we know whether there's a hash-part, an array-part, and a tag. 330 | -- Tag and hash-part are already printed if they're present. 331 | if not has_tag and not has_hash and not has_arr then acc "{ }"; 332 | elseif has_tag and not has_hash and not has_arr then -- nothing, tag already in acc 333 | else 334 | assert (has_hash or has_arr) 335 | local no_brace = false 336 | if has_hash and has_arr then acc ", " 337 | elseif has_tag and not has_hash and alen==1 and type(adt[1])~="table" then 338 | -- No brace required; don't print "{", remember not to print "}" 339 | acc (" "); rec (adt[1], nested, indent+(FIX_INDENT or 0)) 340 | no_brace = true 341 | elseif not has_hash then 342 | -- Braces required, but not opened by hash-part handler yet 343 | acc "{ " 344 | if not FIX_INDENT then indent = current_offset end 345 | end 346 | 347 | -- 2nd pass: array-part 348 | if not no_brace and has_arr then 349 | rec (adt[1], nested, indent+(FIX_INDENT or 0)) 350 | for i=2, alen do 351 | acc ", "; 352 | if current_offset + xlen (adt[i], { }) > LINE_MAX 353 | then acc_newline() end 354 | rec (adt[i], nested, indent+(FIX_INDENT or 0)) 355 | end 356 | end 357 | if not no_brace then acc " }" end 358 | end 359 | nested[adt] = false -- No more nested calls 360 | end 361 | local y = x[type(adt)] 362 | if y then y() else acc(tostring(adt)) end 363 | end 364 | --printf("INITIAL_INDENT = %i", INITIAL_INDENT) 365 | current_offset = INITIAL_INDENT or 0 366 | rec(t, { }, 0) 367 | return table.concat (acc_list) 368 | end 369 | 370 | function table.print(...) return print(table.tostring(...)) end 371 | 372 | return table -------------------------------------------------------------------------------- /misc/luainspect/metalualib/mlp_expr.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------- 2 | -- Metalua: $Id: mlp_expr.lua,v 1.7 2006/11/15 09:07:50 fab13n Exp $ 3 | -- 4 | -- Summary: metalua parser, expression parser. This is part of the 5 | -- definition of module [mlp]. 6 | -- 7 | ---------------------------------------------------------------------- 8 | -- 9 | -- Copyright (c) 2006, Fabien Fleutot . 10 | -- 11 | -- This software is released under the MIT Licence, see licence.txt 12 | -- for details. 13 | -- 14 | ---------------------------------------------------------------------- 15 | -- History: 16 | -- $Log: mlp_expr.lua,v $ 17 | -- Revision 1.7 2006/11/15 09:07:50 fab13n 18 | -- debugged meta operators. 19 | -- Added command line options handling. 20 | -- 21 | -- Revision 1.6 2006/11/10 02:11:17 fab13n 22 | -- compiler faithfulness to 5.1 improved 23 | -- gg.expr extended 24 | -- mlp.expr refactored 25 | -- 26 | -- Revision 1.5 2006/11/09 09:39:57 fab13n 27 | -- some cleanup 28 | -- 29 | -- Revision 1.4 2006/11/07 21:29:02 fab13n 30 | -- improved quasi-quoting 31 | -- 32 | -- Revision 1.3 2006/11/07 04:38:00 fab13n 33 | -- first bootstrapping version. 34 | -- 35 | -- Revision 1.2 2006/11/05 15:08:34 fab13n 36 | -- updated code generation, to be compliant with 5.1 37 | -- 38 | ---------------------------------------------------------------------- 39 | 40 | -------------------------------------------------------------------------------- 41 | -- 42 | -- Exported API: 43 | -- * [mlp.expr()] 44 | -- * [mlp.expr_list()] 45 | -- * [mlp.func_val()] 46 | -- 47 | -------------------------------------------------------------------------------- 48 | 49 | --require "gg" 50 | --require "mlp_misc" 51 | --require "mlp_table" 52 | --require "mlp_meta" 53 | 54 | -------------------------------------------------------------------------------- 55 | -- These function wrappers (eta-expansions ctually) are just here to break 56 | -- some circular dependencies between mlp_xxx.lua files. 57 | -------------------------------------------------------------------------------- 58 | local function _expr (lx) return mlp.expr (lx) end 59 | local function _table_content (lx) return mlp.table_content (lx) end 60 | local function block (lx) return mlp.block (lx) end 61 | local function stat (lx) return mlp.stat (lx) end 62 | 63 | module ("mlp", package.seeall) 64 | 65 | -------------------------------------------------------------------------------- 66 | -- Non-empty expression list. Actually, this isn't used here, but that's 67 | -- handy to give to users. 68 | -------------------------------------------------------------------------------- 69 | expr_list = gg.list{ _expr, separators = "," } 70 | 71 | -------------------------------------------------------------------------------- 72 | -- Helpers for function applications / method applications 73 | -------------------------------------------------------------------------------- 74 | func_args_content = gg.list { 75 | name = "function arguments", 76 | _expr, separators = ",", terminators = ")" } 77 | 78 | -- Used to parse methods 79 | method_args = gg.multisequence{ 80 | name = "function argument(s)", 81 | { "{", table_content, "}" }, 82 | { "(", func_args_content, ")", builder = fget(1) }, 83 | default = function(lx) local r = opt_string(lx); return r and {r} or { } end } 84 | 85 | -------------------------------------------------------------------------------- 86 | -- [func_val] parses a function, from opening parameters parenthese to 87 | -- "end" keyword included. Used for anonymous functions as well as 88 | -- function declaration statements (both local and global). 89 | -- 90 | -- It's wrapped in a [_func_val] eta expansion, so that when expr 91 | -- parser uses the latter, they will notice updates of [func_val] 92 | -- definitions. 93 | -------------------------------------------------------------------------------- 94 | func_params_content = gg.list{ name="function parameters", 95 | gg.multisequence{ { "...", builder = "Dots" }, default = id }, 96 | separators = ",", terminators = {")", "|"} } 97 | 98 | local _func_params_content = function (lx) return func_params_content(lx) end 99 | 100 | func_val = gg.sequence { name="function body", 101 | "(", func_params_content, ")", block, "end", builder = "Function" } 102 | 103 | local _func_val = function (lx) return func_val(lx) end 104 | 105 | -------------------------------------------------------------------------------- 106 | -- Default parser for primary expressions 107 | -------------------------------------------------------------------------------- 108 | function id_or_literal (lx) 109 | local a = lx:next() 110 | if a.tag~="Id" and a.tag~="String" and a.tag~="Number" then 111 | gg.parse_error (lx, "Unexpected expr token %s", 112 | _G.table.tostring (a, 'nohash')) 113 | end 114 | return a 115 | end 116 | 117 | 118 | -------------------------------------------------------------------------------- 119 | -- Builder generator for operators. Wouldn't be worth it if "|x|" notation 120 | -- were allowed, but then lua 5.1 wouldn't compile it 121 | -------------------------------------------------------------------------------- 122 | 123 | -- opf1 = |op| |_,a| `Op{ op, a } 124 | local function opf1 (op) return 125 | function (_,a) return { tag="Op", op, a } end end 126 | 127 | -- opf2 = |op| |a,_,b| `Op{ op, a, b } 128 | local function opf2 (op) return 129 | function (a,_,b) return { tag="Op", op, a, b } end end 130 | 131 | -- opf2r = |op| |a,_,b| `Op{ op, b, a } -- (args reversed) 132 | local function opf2r (op) return 133 | function (a,_,b) return { tag="Op", op, b, a } end end 134 | 135 | local function op_ne(a, _, b) 136 | -- The first version guarantees to return the same code as Lua, 137 | -- but it relies on the non-standard 'ne' operator, which has been 138 | -- suppressed from the official AST grammar (although still supported 139 | -- in practice by the compiler). 140 | -- return { tag="Op", "ne", a, b } 141 | return { tag="Op", "not", { tag="Op", "eq", a, b, lineinfo= { 142 | first = a.lineinfo.first, last = b.lineinfo.last } } } 143 | end 144 | 145 | 146 | -------------------------------------------------------------------------------- 147 | -- 148 | -- complete expression 149 | -- 150 | -------------------------------------------------------------------------------- 151 | 152 | -- FIXME: set line number. In [expr] transformers probably 153 | 154 | expr = gg.expr { name = "expression", 155 | 156 | primary = gg.multisequence{ name="expr primary", 157 | { "(", _expr, ")", builder = "Paren" }, 158 | { "function", _func_val, builder = fget(1) }, 159 | { "-{", splice_content, "}", builder = fget(1) }, 160 | { "+{", quote_content, "}", builder = fget(1) }, 161 | { "nil", builder = "Nil" }, 162 | { "true", builder = "True" }, 163 | { "false", builder = "False" }, 164 | { "...", builder = "Dots" }, 165 | table, 166 | default = id_or_literal }, 167 | 168 | infix = { name="expr infix op", 169 | { "+", prec = 60, builder = opf2 "add" }, 170 | { "-", prec = 60, builder = opf2 "sub" }, 171 | { "*", prec = 70, builder = opf2 "mul" }, 172 | { "/", prec = 70, builder = opf2 "div" }, 173 | { "%", prec = 70, builder = opf2 "mod" }, 174 | { "^", prec = 90, builder = opf2 "pow", assoc = "right" }, 175 | { "..", prec = 40, builder = opf2 "concat", assoc = "right" }, 176 | { "==", prec = 30, builder = opf2 "eq" }, 177 | { "~=", prec = 30, builder = op_ne }, 178 | { "<", prec = 30, builder = opf2 "lt" }, 179 | { "<=", prec = 30, builder = opf2 "le" }, 180 | { ">", prec = 30, builder = opf2r "lt" }, 181 | { ">=", prec = 30, builder = opf2r "le" }, 182 | { "and",prec = 20, builder = opf2 "and" }, 183 | { "or", prec = 10, builder = opf2 "or" } }, 184 | 185 | prefix = { name="expr prefix op", 186 | { "not", prec = 80, builder = opf1 "not" }, 187 | { "#", prec = 80, builder = opf1 "len" }, 188 | { "-", prec = 80, builder = opf1 "unm" } }, 189 | 190 | suffix = { name="expr suffix op", 191 | { "[", _expr, "]", builder = function (tab, idx) 192 | return {tag="Index", tab, idx[1]} end}, 193 | { ".", id, builder = function (tab, field) 194 | return {tag="Index", tab, id2string(field[1])} end }, 195 | { "(", func_args_content, ")", builder = function(f, args) 196 | return {tag="Call", f, unpack(args[1])} end }, 197 | { "{", _table_content, "}", builder = function (f, arg) 198 | return {tag="Call", f, arg[1]} end}, 199 | { ":", id, method_args, builder = function (obj, post) 200 | return {tag="Invoke", obj, id2string(post[1]), unpack(post[2])} end}, 201 | { "+{", quote_content, "}", builder = function (f, arg) 202 | return {tag="Call", f, arg[1] } end }, 203 | default = { name="opt_string_arg", parse = mlp.opt_string, builder = function(f, arg) 204 | return {tag="Call", f, arg } end } } } 205 | -------------------------------------------------------------------------------- /misc/luainspect/metalualib/mlp_ext.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- 3 | -- Non-Lua syntax extensions 4 | -- 5 | -------------------------------------------------------------------------------- 6 | 7 | module ("mlp", package.seeall) 8 | 9 | -------------------------------------------------------------------------------- 10 | -- Alebraic Datatypes 11 | -------------------------------------------------------------------------------- 12 | local function adt (lx) 13 | local tagval = id (lx) [1] 14 | local tagkey = {tag="Pair", {tag="String", "tag"}, {tag="String", tagval} } 15 | if lx:peek().tag == "String" or lx:peek().tag == "Number" then 16 | return { tag="Table", tagkey, lx:next() } 17 | elseif lx:is_keyword (lx:peek(), "{") then 18 | local x = table (lx) 19 | _G.table.insert (x, 1, tagkey) 20 | return x 21 | else return { tag="Table", tagkey } end 22 | end 23 | 24 | expr:add{ "`", adt, builder = fget(1) } 25 | 26 | -------------------------------------------------------------------------------- 27 | -- Anonymous lambda 28 | -------------------------------------------------------------------------------- 29 | local lambda_expr = gg.sequence{ 30 | "|", func_params_content, "|", expr, 31 | builder= function (x) 32 | local li = x[2].lineinfo 33 | return { tag="Function", x[1], 34 | { {tag="Return", x[2], lineinfo=li }, lineinfo=li } } 35 | end } 36 | 37 | -- In an earlier version, lambda_expr took an expr_list rather than an expr 38 | -- after the 2nd bar. However, it happened to be much more of a burden than an 39 | -- help, So finally I disabled it. If you want to return several results, 40 | -- use the long syntax. 41 | -------------------------------------------------------------------------------- 42 | -- local lambda_expr = gg.sequence{ 43 | -- "|", func_params_content, "|", expr_list, 44 | -- builder= function (x) 45 | -- return {tag="Function", x[1], { {tag="Return", unpack(x[2]) } } } end } 46 | 47 | expr:add (lambda_expr) 48 | 49 | -------------------------------------------------------------------------------- 50 | -- Allows to write "a `f` b" instead of "f(a, b)". Taken from Haskell. 51 | -- This is not part of Lua 5.1 syntax, so it's added to the expression 52 | -- afterwards, so that it's easier to disable. 53 | -------------------------------------------------------------------------------- 54 | local function expr_in_backquotes (lx) return expr(lx, 35) end 55 | 56 | expr.infix:add{ name = "infix function", 57 | "`", expr_in_backquotes, "`", prec = 35, assoc="left", 58 | builder = function(a, op, b) return {tag="Call", op[1], a, b} end } 59 | 60 | 61 | -------------------------------------------------------------------------------- 62 | -- table.override assignment 63 | -------------------------------------------------------------------------------- 64 | 65 | mlp.lexer:add "<-" 66 | stat.assignments["<-"] = function (a, b) 67 | assert( #a==1 and #b==1, "No multi-args for '<-'") 68 | return { tag="Call", { tag="Index", { tag="Id", "table" }, 69 | { tag="String", "override" } }, 70 | a[1], b[1]} 71 | end 72 | 73 | -------------------------------------------------------------------------------- 74 | -- C-style op+assignments 75 | -------------------------------------------------------------------------------- 76 | local function op_assign(kw, op) 77 | local function rhs(a, b) 78 | return { tag="Op", op, a, b } 79 | end 80 | local function f(a,b) 81 | return { tag="Set", a, _G.table.imap(rhs, a, b) } 82 | end 83 | mlp.lexer:add (kw) 84 | mlp.stat.assignments[kw] = f 85 | end 86 | 87 | _G.table.iforeach (op_assign, 88 | {"+=", "-=", "*=", "/="}, 89 | {"add", "sub", "mul", "div"}) -------------------------------------------------------------------------------- /misc/luainspect/metalualib/mlp_lexer.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------- 2 | -- Metalua: $Id: mll.lua,v 1.3 2006/11/15 09:07:50 fab13n Exp $ 3 | -- 4 | -- Summary: Source file lexer. ~~Currently only works on strings. 5 | -- Some API refactoring is needed. 6 | -- 7 | ---------------------------------------------------------------------- 8 | -- 9 | -- Copyright (c) 2006-2007, Fabien Fleutot . 10 | -- 11 | -- This software is released under the MIT Licence, see licence.txt 12 | -- for details. 13 | -- 14 | ---------------------------------------------------------------------- 15 | 16 | module ("mlp", package.seeall) 17 | 18 | require "lexer" 19 | 20 | local mlp_lexer = lexer.lexer:clone() 21 | 22 | local keywords = { 23 | "and", "break", "do", "else", "elseif", 24 | "end", "false", "for", "function", "if", 25 | "in", "local", "nil", "not", "or", "repeat", 26 | "return", "then", "true", "until", "while", 27 | "...", "..", "==", ">=", "<=", "~=", 28 | "+{", "-{" } 29 | 30 | for w in values(keywords) do mlp_lexer:add(w) end 31 | 32 | _M.lexer = mlp_lexer 33 | -------------------------------------------------------------------------------- /misc/luainspect/metalualib/mlp_meta.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------- 2 | -- Metalua: $Id: mlp_meta.lua,v 1.4 2006/11/15 09:07:50 fab13n Exp $ 3 | -- 4 | -- Summary: Meta-operations: AST quasi-quoting and splicing 5 | -- 6 | ---------------------------------------------------------------------- 7 | -- 8 | -- Copyright (c) 2006, Fabien Fleutot . 9 | -- 10 | -- This software is released under the MIT Licence, see licence.txt 11 | -- for details. 12 | -- 13 | ---------------------------------------------------------------------- 14 | 15 | 16 | -------------------------------------------------------------------------------- 17 | -- 18 | -- Exported API: 19 | -- * [mlp.splice_content()] 20 | -- * [mlp.quote_content()] 21 | -- 22 | -------------------------------------------------------------------------------- 23 | 24 | module ("mlp", package.seeall) 25 | 26 | -------------------------------------------------------------------------------- 27 | -- External splicing: compile an AST into a chunk, load and evaluate 28 | -- that chunk, and replace the chunk by its result (which must also be 29 | -- an AST). 30 | -------------------------------------------------------------------------------- 31 | 32 | function splice (ast) 33 | local f = mlc.function_of_ast(ast, '=splice') 34 | local result=f() 35 | return result 36 | end 37 | 38 | -------------------------------------------------------------------------------- 39 | -- Going from an AST to an AST representing that AST 40 | -- the only key being lifted in this version is ["tag"] 41 | -------------------------------------------------------------------------------- 42 | function quote (t) 43 | --print("QUOTING:", _G.table.tostring(t, 60)) 44 | local cases = { } 45 | function cases.table (t) 46 | local mt = { tag = "Table" } 47 | --_G.table.insert (mt, { tag = "Pair", quote "quote", { tag = "True" } }) 48 | if t.tag == "Splice" then 49 | assert (#t==1, "Invalid splice") 50 | local sp = t[1] 51 | return sp 52 | elseif t.tag then 53 | _G.table.insert (mt, { tag = "Pair", quote "tag", quote (t.tag) }) 54 | end 55 | for _, v in ipairs (t) do 56 | _G.table.insert (mt, quote(v)) 57 | end 58 | return mt 59 | end 60 | function cases.number (t) return { tag = "Number", t, quote = true } end 61 | function cases.string (t) return { tag = "String", t, quote = true } end 62 | return cases [ type (t) ] (t) 63 | end 64 | 65 | -------------------------------------------------------------------------------- 66 | -- when this variable is false, code inside [-{...}] is compiled and 67 | -- avaluated immediately. When it's true (supposedly when we're 68 | -- parsing data inside a quasiquote), [-{foo}] is replaced by 69 | -- [`Splice{foo}], which will be unpacked by [quote()]. 70 | -------------------------------------------------------------------------------- 71 | in_a_quote = false 72 | 73 | -------------------------------------------------------------------------------- 74 | -- Parse the inside of a "-{ ... }" 75 | -------------------------------------------------------------------------------- 76 | function splice_content (lx) 77 | local parser_name = "expr" 78 | if lx:is_keyword (lx:peek(2), ":") then 79 | local a = lx:next() 80 | lx:next() -- skip ":" 81 | assert (a.tag=="Id", "Invalid splice parser name") 82 | parser_name = a[1] 83 | end 84 | local ast = mlp[parser_name](lx) 85 | if in_a_quote then 86 | --printf("SPLICE_IN_QUOTE:\n%s", _G.table.tostring(ast, "nohash", 60)) 87 | return { tag="Splice", ast } 88 | else 89 | if parser_name == "expr" then ast = { { tag="Return", ast } } 90 | elseif parser_name == "stat" then ast = { ast } 91 | elseif parser_name ~= "block" then 92 | error ("splice content must be an expr, stat or block") end 93 | --printf("EXEC THIS SPLICE:\n%s", _G.table.tostring(ast, "nohash", 60)) 94 | return splice (ast) 95 | end 96 | end 97 | 98 | -------------------------------------------------------------------------------- 99 | -- Parse the inside of a "+{ ... }" 100 | -------------------------------------------------------------------------------- 101 | function quote_content (lx) 102 | local parser 103 | if lx:is_keyword (lx:peek(2), ":") then -- +{parser: content } 104 | parser = mlp[id(lx)[1]] 105 | lx:next() 106 | else -- +{ content } 107 | parser = mlp.expr 108 | end 109 | 110 | local prev_iq = in_a_quote 111 | in_a_quote = true 112 | --print("IN_A_QUOTE") 113 | local content = parser (lx) 114 | local q_content = quote (content) 115 | in_a_quote = prev_iq 116 | return q_content 117 | end 118 | 119 | -------------------------------------------------------------------------------- /misc/luainspect/metalualib/mlp_misc.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------- 2 | -- Metalua: $Id: mlp_misc.lua,v 1.6 2006/11/15 09:07:50 fab13n Exp $ 3 | -- 4 | -- Summary: metalua parser, miscellaneous utility functions. 5 | -- 6 | ---------------------------------------------------------------------- 7 | -- 8 | -- Copyright (c) 2006, Fabien Fleutot . 9 | -- 10 | -- This software is released under the MIT Licence, see licence.txt 11 | -- for details. 12 | -- 13 | ---------------------------------------------------------------------- 14 | -- History: 15 | -- $Log: mlp_misc.lua,v $ 16 | -- Revision 1.6 2006/11/15 09:07:50 fab13n 17 | -- debugged meta operators. 18 | -- Added command line options handling. 19 | -- 20 | -- Revision 1.5 2006/11/10 02:11:17 fab13n 21 | -- compiler faithfulness to 5.1 improved 22 | -- gg.expr extended 23 | -- mlp.expr refactored 24 | -- 25 | -- Revision 1.4 2006/11/09 09:39:57 fab13n 26 | -- some cleanup 27 | -- 28 | -- Revision 1.3 2006/11/07 04:38:00 fab13n 29 | -- first bootstrapping version. 30 | -- 31 | -- Revision 1.2 2006/11/05 15:08:34 fab13n 32 | -- updated code generation, to be compliant with 5.1 33 | -- 34 | ---------------------------------------------------------------------- 35 | 36 | -------------------------------------------------------------------------------- 37 | -- 38 | -- Exported API: 39 | -- * [mlp.fget()] 40 | -- * [mlp.id()] 41 | -- * [mlp.opt_id()] 42 | -- * [mlp.id_list()] 43 | -- * [mlp.gensym()] 44 | -- * [mlp.string()] 45 | -- * [mlp.opt_string()] 46 | -- * [mlp.id2string()] 47 | -- 48 | -------------------------------------------------------------------------------- 49 | 50 | --require "gg" 51 | --require "mll" 52 | 53 | module ("mlp", package.seeall) 54 | 55 | -------------------------------------------------------------------------------- 56 | -- returns a function that takes the [n]th element of a table. 57 | -- if [tag] is provided, then this element is expected to be a 58 | -- table, and this table receives a "tag" field whose value is 59 | -- set to [tag]. 60 | -- 61 | -- The primary purpose of this is to generate builders for 62 | -- grammar generators. It has little purpose in metalua, as lambda has 63 | -- a lightweight syntax. 64 | -------------------------------------------------------------------------------- 65 | 66 | function fget (n, tag) 67 | assert (type (n) == "number") 68 | if tag then 69 | assert (type (tag) == "string") 70 | return function (x) 71 | assert (type (x[n]) == "table") 72 | return {tag=tag, unpack(x[n])} end 73 | else 74 | return function (x) return x[n] end 75 | end 76 | end 77 | 78 | 79 | -------------------------------------------------------------------------------- 80 | -- Try to read an identifier (possibly as a splice), or return [false] if no 81 | -- id is found. 82 | -------------------------------------------------------------------------------- 83 | function opt_id (lx) 84 | local a = lx:peek(); 85 | if lx:is_keyword (a, "-{") then 86 | local v = gg.sequence{ "-{", splice_content, "}" } (lx) [1] 87 | if v.tag ~= "Id" and v.tag ~= "Splice" then 88 | gg.parse_error(lx,"Bad id splice") 89 | end 90 | return v 91 | elseif a.tag == "Id" then return lx:next() 92 | else return false end 93 | end 94 | 95 | -------------------------------------------------------------------------------- 96 | -- Mandatory reading of an id: causes an error if it can't read one. 97 | -------------------------------------------------------------------------------- 98 | function id (lx) 99 | return opt_id (lx) or gg.parse_error(lx,"Identifier expected") 100 | end 101 | 102 | -------------------------------------------------------------------------------- 103 | -- Common helper function 104 | -------------------------------------------------------------------------------- 105 | id_list = gg.list { primary = mlp.id, separators = "," } 106 | 107 | -------------------------------------------------------------------------------- 108 | -- Symbol generator: [gensym()] returns a guaranteed-to-be-unique identifier. 109 | -- The main purpose is to avoid variable capture in macros. 110 | -- 111 | -- If a string is passed as an argument, theis string will be part of the 112 | -- id name (helpful for macro debugging) 113 | -------------------------------------------------------------------------------- 114 | local gensymidx = 0 115 | 116 | function gensym (arg) 117 | gensymidx = gensymidx + 1 118 | return { tag="Id", _G.string.format(".%i.%s", gensymidx, arg or "")} 119 | end 120 | 121 | -------------------------------------------------------------------------------- 122 | -- Converts an identifier into a string. Hopefully one day it'll handle 123 | -- splices gracefully, but that proves quite tricky. 124 | -------------------------------------------------------------------------------- 125 | function id2string (id) 126 | --print("id2string:", disp.ast(id)) 127 | if id.tag == "Id" then id.tag = "String"; return id 128 | elseif id.tag == "Splice" then 129 | assert (in_a_quote, "can't do id2string on an outermost splice") 130 | error ("id2string on splice not implemented") 131 | -- Evaluating id[1] will produce `Id{ xxx }, 132 | -- and we want it to produce `String{ xxx } 133 | -- Morally, this is what I want: 134 | -- return `String{ `Index{ `Splice{ id[1] }, `Number 1 } } 135 | -- That is, without sugar: 136 | return {tag="String", {tag="Index", {tag="Splice", id[1] }, 137 | {tag="Number", 1 } } } 138 | else error ("Identifier expected: "..table.tostring(id)) end 139 | end 140 | 141 | -------------------------------------------------------------------------------- 142 | -- Read a string, possibly spliced, or return an error if it can't 143 | -------------------------------------------------------------------------------- 144 | function string (lx) 145 | local a = lx:peek() 146 | if lx:is_keyword (a, "-{") then 147 | local v = gg.sequence{ "-{", splice_content, "}" } (lx) [1] 148 | if v.tag ~= "" and v.tag ~= "Splice" then 149 | gg.parse_error(lx,"Bad string splice") 150 | end 151 | return v 152 | elseif a.tag == "String" then return lx:next() 153 | else error "String expected" end 154 | end 155 | 156 | -------------------------------------------------------------------------------- 157 | -- Try to read a string, or return false if it can't. No splice allowed. 158 | -------------------------------------------------------------------------------- 159 | function opt_string (lx) 160 | return lx:peek().tag == "String" and lx:next() 161 | end 162 | 163 | -------------------------------------------------------------------------------- 164 | -- Chunk reader: block + Eof 165 | -------------------------------------------------------------------------------- 166 | function skip_initial_sharp_comment (lx) 167 | -- Dirty hack: I'm happily fondling lexer's private parts 168 | -- FIXME: redundant with lexer:newstream() 169 | lx :sync() 170 | local i = lx.src:match ("^#.-\n()", lx.i) 171 | if i then lx.i, lx.column_offset, lx.line = i, i, lx.line+1 end 172 | end 173 | 174 | local function _chunk (lx) 175 | if lx:peek().tag == 'Eof' then return { } -- handle empty files 176 | else 177 | skip_initial_sharp_comment (lx) 178 | local chunk = block (lx) 179 | if lx:peek().tag ~= "Eof" then error "End-of-file expected" end 180 | return chunk 181 | end 182 | end 183 | 184 | -- chunk is wrapped in a sequence so that it has a "transformer" field. 185 | chunk = gg.sequence { _chunk, builder = unpack } -------------------------------------------------------------------------------- /misc/luainspect/metalualib/mlp_stat.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------- 2 | -- Metalua: $Id: mlp_stat.lua,v 1.7 2006/11/15 09:07:50 fab13n Exp $ 3 | -- 4 | -- Summary: metalua parser, statement/block parser. This is part of 5 | -- the definition of module [mlp]. 6 | -- 7 | ---------------------------------------------------------------------- 8 | -- 9 | -- Copyright (c) 2006, Fabien Fleutot . 10 | -- 11 | -- This software is released under the MIT Licence, see licence.txt 12 | -- for details. 13 | -- 14 | ---------------------------------------------------------------------- 15 | -- 16 | ---------------------------------------------------------------------- 17 | 18 | -------------------------------------------------------------------------------- 19 | -- 20 | -- Exports API: 21 | -- * [mlp.stat()] 22 | -- * [mlp.block()] 23 | -- * [mlp.for_header()] 24 | -- 25 | -------------------------------------------------------------------------------- 26 | 27 | -------------------------------------------------------------------------------- 28 | -- eta-expansions to break circular dependency 29 | -------------------------------------------------------------------------------- 30 | local expr = function (lx) return mlp.expr (lx) end 31 | local func_val = function (lx) return mlp.func_val (lx) end 32 | local expr_list = function (lx) return mlp.expr_list(lx) end 33 | 34 | module ("mlp", package.seeall) 35 | 36 | -------------------------------------------------------------------------------- 37 | -- List of all keywords that indicate the end of a statement block. Users are 38 | -- likely to extend this list when designing extensions. 39 | -------------------------------------------------------------------------------- 40 | 41 | 42 | local block_terminators = { "else", "elseif", "end", "until", ")", "}", "]" } 43 | 44 | -- FIXME: this must be handled from within GG!!! 45 | function block_terminators:add(x) 46 | if type (x) == "table" then for _, y in ipairs(x) do self:add (y) end 47 | else _G.table.insert (self, x) end 48 | end 49 | 50 | -------------------------------------------------------------------------------- 51 | -- list of statements, possibly followed by semicolons 52 | -------------------------------------------------------------------------------- 53 | block = gg.list { 54 | name = "statements block", 55 | terminators = block_terminators, 56 | primary = function (lx) 57 | -- FIXME use gg.optkeyword() 58 | local x = stat (lx) 59 | if lx:is_keyword (lx:peek(), ";") then lx:next() end 60 | return x 61 | end } 62 | 63 | -------------------------------------------------------------------------------- 64 | -- Helper function for "return " parsing. 65 | -- Called when parsing return statements. 66 | -- The specific test for initial ";" is because it's not a block terminator, 67 | -- so without itgg.list would choke on "return ;" statements. 68 | -- We don't make a modified copy of block_terminators because this list 69 | -- is sometimes modified at runtime, and the return parser would get out of 70 | -- sync if it was relying on a copy. 71 | -------------------------------------------------------------------------------- 72 | local return_expr_list_parser = gg.multisequence{ 73 | { ";" , builder = function() return { } end }, 74 | default = gg.list { 75 | expr, separators = ",", terminators = block_terminators } } 76 | 77 | -------------------------------------------------------------------------------- 78 | -- for header, between [for] and [do] (exclusive). 79 | -- Return the `Forxxx{...} AST, without the body element (the last one). 80 | -------------------------------------------------------------------------------- 81 | function for_header (lx) 82 | local var = mlp.id (lx) 83 | if lx:is_keyword (lx:peek(), "=") then 84 | -- Fornum: only 1 variable 85 | lx:next() -- skip "=" 86 | local e = expr_list (lx) 87 | assert (2 <= #e and #e <= 3, "2 or 3 values in a fornum") 88 | return { tag="Fornum", var, unpack (e) } 89 | else 90 | -- Forin: there might be several vars 91 | local a = lx:is_keyword (lx:next(), ",", "in") 92 | if a=="in" then var_list = { var, lineinfo = var.lineinfo } else 93 | -- several vars; first "," skipped, read other vars 94 | var_list = gg.list{ 95 | primary = id, separators = ",", terminators = "in" } (lx) 96 | _G.table.insert (var_list, 1, var) -- put back the first variable 97 | var_list.lineinfo.first = var.lineinfo.first 98 | --PATCHED:LuaInspect:correct lineinfo, e.g. `for a,b in f do end` 99 | lx:next() -- skip "in" 100 | end 101 | local e = expr_list (lx) 102 | return { tag="Forin", var_list, e } 103 | end 104 | end 105 | 106 | -------------------------------------------------------------------------------- 107 | -- Function def parser helper: id ( . id ) * 108 | -------------------------------------------------------------------------------- 109 | local function fn_builder (list) 110 | local r = list[1] 111 | for i = 2, #list do r = { tag="Index", r, id2string(list[i]), 112 | lineinfo={first=list[1].lineinfo.first, last=list[i].lineinfo.last} } end 113 | --PATCHED:LuaInspect:added lineinfo to above line. e.g. `function a.b.c() end` 114 | return r 115 | end 116 | local func_name = gg.list{ id, separators = ".", builder = fn_builder } 117 | 118 | -------------------------------------------------------------------------------- 119 | -- Function def parser helper: ( : id )? 120 | -------------------------------------------------------------------------------- 121 | local method_name = gg.onkeyword{ name = "method invocation", ":", id, 122 | transformers = { function(x) return x and id2string(x) end } } 123 | 124 | -------------------------------------------------------------------------------- 125 | -- Function def builder 126 | -------------------------------------------------------------------------------- 127 | local function funcdef_builder(x) 128 | local name, method, func = x[1], x[2], x[3] 129 | if method then 130 | name = { tag="Index", name, method, lineinfo = { 131 | first = name.lineinfo.first, 132 | last = method.lineinfo.last } } 133 | _G.table.insert (func[1], 1, {tag="Id", "self"}) 134 | end 135 | local r = { tag="Set", {name}, {func} } 136 | r[1].lineinfo = name.lineinfo 137 | r[2].lineinfo = func.lineinfo 138 | return r 139 | end 140 | 141 | 142 | -------------------------------------------------------------------------------- 143 | -- if statement builder 144 | -------------------------------------------------------------------------------- 145 | local function if_builder (x) 146 | local cb_pairs, else_block, r = x[1], x[2], {tag="If"} 147 | for i=1,#cb_pairs do r[2*i-1]=cb_pairs[i][1]; r[2*i]=cb_pairs[i][2] end 148 | if else_block then r[#r+1] = else_block end 149 | return r 150 | end 151 | 152 | -------------------------------------------------------------------------------- 153 | -- produce a list of (expr,block) pairs 154 | -------------------------------------------------------------------------------- 155 | local elseifs_parser = gg.list { 156 | gg.sequence { expr, "then", block }, 157 | separators = "elseif", 158 | terminators = { "else", "end" } } 159 | 160 | -------------------------------------------------------------------------------- 161 | -- assignments and calls: statements that don't start with a keyword 162 | -------------------------------------------------------------------------------- 163 | local function assign_or_call_stat_parser (lx) 164 | local e = expr_list (lx) 165 | local a = lx:is_keyword(lx:peek()) 166 | local op = a and stat.assignments[a] 167 | if op then 168 | --FIXME: check that [e] is a LHS 169 | lx:next() 170 | local v = expr_list (lx) 171 | if type(op)=="string" then return { tag=op, e, v } 172 | else return op (e, v) end 173 | else 174 | assert (#e > 0) 175 | if #e > 1 then 176 | gg.parse_error (lx, "comma is not a valid statement separator") end 177 | if e[1].tag ~= "Call" and e[1].tag ~= "Invoke" then 178 | gg.parse_error (lx, "This expression is of type '%s'; ".. 179 | "only function and method calls make valid statements", 180 | e[1].tag or "") 181 | end 182 | return e[1] 183 | end 184 | end 185 | 186 | local_stat_parser = gg.multisequence{ 187 | -- local function 188 | { "function", id, func_val, builder = 189 | function(x) 190 | local vars = { x[1], lineinfo = x[1].lineinfo } 191 | local vals = { x[2], lineinfo = x[2].lineinfo } 192 | return { tag="Localrec", vars, vals } 193 | end }, 194 | -- local ( = )? 195 | default = gg.sequence{ id_list, gg.onkeyword{ "=", expr_list }, 196 | builder = function(x) return {tag="Local", x[1], x[2] or { } } end } } 197 | 198 | -------------------------------------------------------------------------------- 199 | -- statement 200 | -------------------------------------------------------------------------------- 201 | stat = gg.multisequence { 202 | name="statement", 203 | { "do", block, "end", builder = 204 | function (x) return { tag="Do", unpack (x[1]) } end }, 205 | { "for", for_header, "do", block, "end", builder = 206 | function (x) x[1][#x[1]+1] = x[2]; return x[1] end }, 207 | { "function", func_name, method_name, func_val, builder=funcdef_builder }, 208 | { "while", expr, "do", block, "end", builder = "While" }, 209 | { "repeat", block, "until", expr, builder = "Repeat" }, 210 | { "local", local_stat_parser, builder = fget (1) }, 211 | { "return", return_expr_list_parser, builder = fget (1, "Return") }, 212 | { "break", builder = function() return { tag="Break" } end }, 213 | { "-{", splice_content, "}", builder = fget(1) }, 214 | { "if", elseifs_parser, gg.onkeyword{ "else", block }, "end", 215 | builder = if_builder }, 216 | default = assign_or_call_stat_parser } 217 | 218 | stat.assignments = { 219 | ["="] = "Set" } 220 | 221 | function stat.assignments:add(k, v) self[k] = v end 222 | -------------------------------------------------------------------------------- /misc/luainspect/metalualib/mlp_table.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------- 2 | -- Metalua: $Id: mlp_table.lua,v 1.5 2006/11/10 02:11:17 fab13n Exp $ 3 | -- 4 | -- Summary: metalua parser, table constructor parser. This is part 5 | -- of thedefinition of module [mlp]. 6 | -- 7 | ---------------------------------------------------------------------- 8 | -- 9 | -- Copyright (c) 2006, Fabien Fleutot . 10 | -- 11 | -- This software is released under the MIT Licence, see licence.txt 12 | -- for details. 13 | -- 14 | ---------------------------------------------------------------------- 15 | -- History: 16 | -- $Log: mlp_table.lua,v $ 17 | -- Revision 1.5 2006/11/10 02:11:17 fab13n 18 | -- compiler faithfulness to 5.1 improved 19 | -- gg.expr extended 20 | -- mlp.expr refactored 21 | -- 22 | -- Revision 1.4 2006/11/09 09:39:57 fab13n 23 | -- some cleanup 24 | -- 25 | -- Revision 1.3 2006/11/07 04:38:00 fab13n 26 | -- first bootstrapping version. 27 | -- 28 | -- Revision 1.2 2006/11/05 15:08:34 fab13n 29 | -- updated code generation, to be compliant with 5.1 30 | -- 31 | ---------------------------------------------------------------------- 32 | 33 | -------------------------------------------------------------------------------- 34 | -- 35 | -- Exported API: 36 | -- * [mlp.table_field()] 37 | -- * [mlp.table_content()] 38 | -- * [mlp.table()] 39 | -- 40 | -- KNOWN BUG: doesn't handle final ";" or "," before final "}" 41 | -- 42 | -------------------------------------------------------------------------------- 43 | 44 | --require "gg" 45 | --require "mll" 46 | --require "mlp_misc" 47 | 48 | module ("mlp", package.seeall) 49 | 50 | -------------------------------------------------------------------------------- 51 | -- eta expansion to break circular dependencies: 52 | -------------------------------------------------------------------------------- 53 | local function _expr (lx) return expr(lx) end 54 | 55 | -------------------------------------------------------------------------------- 56 | -- [[key] = value] table field definition 57 | -------------------------------------------------------------------------------- 58 | local bracket_field = gg.sequence{ "[", _expr, "]", "=", _expr, builder = "Pair" } 59 | 60 | -------------------------------------------------------------------------------- 61 | -- [id = value] or [value] table field definition; 62 | -- [[key]=val] are delegated to [bracket_field()] 63 | -------------------------------------------------------------------------------- 64 | function table_field (lx) 65 | if lx:is_keyword (lx:peek(), "[") then return bracket_field (lx) end 66 | local e = _expr (lx) 67 | if lx:is_keyword (lx:peek(), "=") then 68 | lx:next(); -- skip the "=" 69 | local key = id2string(e) 70 | local val = _expr(lx) 71 | local r = { tag="Pair", key, val } 72 | r.lineinfo = { first = key.lineinfo.first, last = val.lineinfo.last } 73 | return r 74 | else return e end 75 | end 76 | 77 | local function _table_field(lx) return table_field(lx) end 78 | 79 | -------------------------------------------------------------------------------- 80 | -- table constructor, without enclosing braces; returns a full table object 81 | -------------------------------------------------------------------------------- 82 | table_content = gg.list { _table_field, 83 | separators = { ",", ";" }, terminators = "}", builder = "Table" } 84 | 85 | local function _table_content(lx) return table_content(lx) end 86 | 87 | -------------------------------------------------------------------------------- 88 | -- complete table constructor including [{...}] 89 | -------------------------------------------------------------------------------- 90 | table = gg.sequence{ "{", _table_content, "}", builder = fget(1) } 91 | 92 | 93 | -------------------------------------------------------------------------------- /plugin/luainspect.vim: -------------------------------------------------------------------------------- 1 | " Vim plug-in 2 | " Author: Peter Odding 3 | " Last Change: June 17, 2014 4 | " URL: http://peterodding.com/code/vim/lua-inspect/ 5 | 6 | " Support for automatic update using the GLVS plug-in. 7 | " GetLatestVimScripts: 3169 1 :AutoInstall: luainspect.zip 8 | 9 | " Don't source the plug-in when it's already been loaded or &compatible is set. 10 | if &cp || exists('g:loaded_luainspect') 11 | finish 12 | endif 13 | 14 | " Make sure vim-misc is installed. 15 | try 16 | " The point of this code is to do something completely innocent while making 17 | " sure the vim-misc plug-in is installed. We specifically don't use Vim's 18 | " exists() function because it doesn't load auto-load scripts that haven't 19 | " already been loaded yet (last tested on Vim 7.3). 20 | call type(g:xolox#misc#version) 21 | catch 22 | echomsg "Warning: The vim-lua-inspect plug-in requires the vim-misc plug-in which seems not to be installed! For more information please review the installation instructions in the readme (also available on the homepage and on GitHub). The vim-lua-inspect plug-in will now be disabled." 23 | let g:loaded_luainspect = 1 24 | finish 25 | endtry 26 | 27 | if !exists('g:lua_inspect_mappings') 28 | " Change this to disable default key mappings. 29 | let g:lua_inspect_mappings = 1 30 | endif 31 | 32 | if !exists('g:lua_inspect_warnings') 33 | " Change this to disable automatic warning messages. 34 | let g:lua_inspect_warnings = 1 35 | endif 36 | 37 | if !exists('g:lua_inspect_events') 38 | " Change this to enable semantic highlighting on your preferred events. 39 | let g:lua_inspect_events = 'CursorHold,CursorHoldI,BufWritePost' 40 | endif 41 | 42 | if !exists('g:lua_inspect_path') 43 | " Change this if you want to move the Lua modules somewhere else. 44 | let g:lua_inspect_path = expand(':p:h') . '/../misc/luainspect' 45 | endif 46 | 47 | if !exists('g:lua_inspect_internal') 48 | " Set this to false (0) to run LuaInspect as an external process instead of 49 | " using the Lua interface for Vim. This makes it slower but might make it 50 | " more accurate because the Lua interface for Vim didn't include io.* and 51 | " os.* before the patch posted on 2010-07-28 which means LuaInspect would 52 | " mark them as undefined globals. The patch I'm referring to is: 53 | " http://groups.google.com/group/vim_dev/browse_frm/thread/9b77afa2fe4336c8 54 | let g:lua_inspect_internal = has('lua') 55 | endif 56 | 57 | " This command enables/updates highlighting when automatic highlighting is disabled. 58 | command! -bar -bang LuaInspect call xolox#luainspect#highlight_cmd( == '!') 59 | 60 | " This command can be used as a toggle to enable/disable the highlighting. 61 | command! -bar LuaInspectToggle call xolox#luainspect#toggle_cmd() 62 | 63 | " This command can be used to rename variables. 64 | command! -bar LuaInspectRename call xolox#luainspect#make_request('rename') 65 | 66 | " This command can be used to jump to a variable's definition. 67 | command! -bar LuaInspectGoTo call xolox#luainspect#make_request('go_to') 68 | 69 | " Automatically enable the plug-in in Lua buffers. 70 | augroup PluginLuaInspect 71 | autocmd! FileType lua call xolox#luainspect#auto_enable() 72 | augroup END 73 | 74 | " The &balloonexpr option requires a global function. 75 | function! LuaInspectToolTip() 76 | let result = xolox#luainspect#make_request('tooltip') 77 | if exists('b:luainspect_syntax_error') 78 | return b:luainspect_syntax_error 79 | else 80 | return type(result) == type('') ? result : '' 81 | endif 82 | endfunction 83 | 84 | " Make sure the plug-in is only loaded once. 85 | let g:loaded_luainspect = 1 86 | 87 | " vim: ts=2 sw=2 et 88 | --------------------------------------------------------------------------------