Compare commits

...

57 Commits
0.28 ... 0.29

Author SHA1 Message Date
Paul Kulchenko
c1961e5c4b Added Ctrl(-Shift)-TAB navigation between tabs in the editor 2012-05-31 14:20:48 -07:00
Paul Kulchenko
3ead2966d8 Fixed localization of variables based on static analysis 2012-05-30 13:15:58 -07:00
Paul Kulchenko
0bcf590f45 Added reporting of assignment to global variables in the code analyzer 2012-05-30 11:27:17 -07:00
Paul Kulchenko
e8f308ca08 Added ability to turn external processes that connect to debugger into a scratchpad; fixed 'exit' to make it work for remote applications; upgraded to MobDebug 0.449 2012-05-26 23:02:20 -07:00
Paul Kulchenko
ecb59ceb9e Updated comment styling to follow markdown syntax 2012-05-15 23:05:26 -07:00
Paul Kulchenko
a317f986e1 Fixed styling in markup module that wasn't always working when a block comment was uncommented 2012-05-14 19:01:40 -07:00
Paul Kulchenko
623905c81c Added navigation between editor tabs using Ctrl-PgUp and Ctrl-PgDn 2012-05-13 23:10:25 -07:00
Paul Kulchenko
11b702ac6e Added exit from full screen mode using ESC key 2012-05-13 22:38:17 -07:00
Paul Kulchenko
f926aef2f8 Updated MANIFEST with luainspect module files and luasocket module reorg 2012-05-13 22:36:12 -07:00
Paul Kulchenko
3754406e85 Merge branch 'scratchpad' 2012-05-11 16:39:21 -07:00
Paul Kulchenko
981bccbe27 Added menu shortcut for Run As Scratchpad 2012-05-11 14:56:35 -07:00
Paul Kulchenko
047a9e8ac5 Upgraded to Mobdebug 0.448 to fix handling of scripts with comments in the remote shell 2012-05-11 14:30:58 -07:00
Paul Kulchenko
d1f2e8450a Fixed an issue with Analyze process when the analyzed script has compilation errors 2012-05-07 23:01:31 -07:00
Paul Kulchenko
21cfba9cb6 Added reporting of compilation errors during debugging sessions (when external client connects to the debugger and the current script has compilation errors) 2012-05-07 22:56:30 -07:00
Paul Kulchenko
3f711373ed Added handling of more error in the shell to allow calculations like '(1+2)' to be executed correctly (both locally and remotely) 2012-05-07 22:25:22 -07:00
Paul Kulchenko
00fe05e89f Fixed an earlier introduced bug with Run/Debug proceeding after failed compilation; removed references to internal code in compilation errors (in scratchpad) 2012-05-06 18:27:15 -07:00
Paul Kulchenko
3bcd54bd46 Added moving focus back to the notebook after unhiding/activating a wx window 2012-05-06 15:28:16 -07:00
Paul Kulchenko
8de9e41fd6 Updated README 2012-05-06 14:34:45 -07:00
Paul Kulchenko
29b6755a9b Fixed cursor to change to a default one (instead of an arrow) after number sliding 2012-05-06 14:29:49 -07:00
Paul Kulchenko
7d6be282f1 Updated clearing 'output' window to minimize showing duplicate output when running as scratchpad 2012-05-06 14:20:59 -07:00
Paul Kulchenko
f89fd4d752 Added handling of negative numbers for number sliders in scratchpad; added logic to ignore malformed numbers (they are still styled though) 2012-05-05 23:11:22 -07:00
Paul Kulchenko
03adda1cef Added stopping debugger and scratchpad when a window with debugging/scratchpad action is closed 2012-05-05 18:42:12 -07:00
Paul Kulchenko
2992424e87 Fixed an issue with scratchpad being on after Save dialog is canceled 2012-05-05 17:16:24 -07:00
Paul Kulchenko
e302a97682 Added support for using different debugger calls and moved scratchpad to a different debugger for performance reasons; upgraded to Mobdebug 0.446 2012-05-05 14:20:55 -07:00
Paul Kulchenko
7b5d37d595 Added mouse capture for slider operations in scratchpad to increase the range of available values 2012-05-03 16:40:47 -07:00
Paul Kulchenko
01ae85dc5a Reorganized the scratchpad code and added scratchpad teardown code to run when the debugged application is closed 2012-05-03 16:27:54 -07:00
Paul Kulchenko
f650d8f64f Added clearing output window when requested and formatting for compile/execution error messages 2012-05-03 12:58:39 -07:00
Paul Kulchenko
602f8ef223 Fixed debugging menu items to be hidden when running as scratchpad 2012-05-03 12:22:03 -07:00
Paul Kulchenko
6afc999b75 Added number slider processing with dynamic reloading of scratchpad scripts; upgraded to MobDebug 0.445 for better stability 2012-05-03 10:25:25 -07:00
Paul Kulchenko
2ccd214a1f Added stopping criteria to allow execution of any scripts (even empty ones) under 'Run as Scratchpad'; removed stack trace from remote error messages in the scratchpad 2012-05-02 10:45:59 -07:00
Paul Kulchenko
8efde0ec1f Added a check to turn 'Run as Scratchpad' on even when the initial compilation fails 2012-05-02 10:16:30 -07:00
Paul Kulchenko
1c5b14870c Updated event logic in the scratchpad to make sure editor change events do not get lost while an earlier change is being worked on 2012-05-02 00:09:48 -07:00
Paul Kulchenko
2c87909920 Disabled 'Run as Scratchpad' option when the script is already running 2012-05-01 22:00:46 -07:00
Paul Kulchenko
2a2a3bed96 Added reporting of run-time errors when running as scratchpad; upgraded to MobDebug 0.444 2012-05-01 21:49:45 -07:00
Paul Kulchenko
e2f65bced5 Fixed an issue with frequent modifications in a scratchpad; added safety check to avoid reloads from loaded modules (like wx) 2012-04-30 13:49:33 -07:00
Paul Kulchenko
c979d60d28 Moved the logic to disable checkbox for scratchpad menu to IDLE event as EVT_UPDATE_UI is only executed when the user opens up the menu (menu is updated) 2012-04-29 23:08:29 -07:00
Paul Kulchenko
7954ff1f64 Fixed about screen 2012-04-29 21:57:47 -07:00
Paul Kulchenko
a3a5c75694 Reorganized scratchpad code to have all its logic in one place 2012-04-29 18:41:23 -07:00
Paul Kulchenko
d6f3b4052b Reimplemented scratchpad functionality using internal processes (similar to how the debugger is done) as IDE was too unstable when running in the same process 2012-04-29 17:26:22 -07:00
Paul Kulchenko
7bc64d90c7 Removed 'error during pre-compilation' message from compile errors 2012-04-28 15:11:42 -07:00
Paul Kulchenko
56d262b753 Added stopping the debugger when a debugged program exits 2012-04-27 16:33:56 -07:00
Paul Kulchenko
a368f0acf9 Merge branch 'master' of github.com:pkulchenko/ZeroBraneStudio 2012-04-23 09:32:45 -07:00
Paul Kulchenko
d4a53733e7 Added to static analysis reporting of unused parameters in functions (the function name is also reported when possible) 2012-04-23 09:16:13 -07:00
Paul Kulchenko
1117e9df9a Disabled warning in static analysis about unused 'self' in methods 2012-04-22 23:31:58 -07:00
Paul Kulchenko
230de3450f Cleaned up warnings based on results of static analysis 2012-04-22 23:29:32 -07:00
Paul Kulchenko
0ae48ce6de Cleaned up warnings based on results of static analysis 2012-04-22 22:04:23 -07:00
Paul Kulchenko
6a90c5e850 Turned 'unhiding' wx window while running or debugging off by default as it was causing performance problems with some applications 2012-04-22 15:39:03 -07:00
Paul Kulchenko
f1ac72b265 Added scratchpad (running live) functionality 2012-04-21 22:29:21 -07:00
Paul Kulchenko
79fd90c986 Removed logic to update project directory based on file selection in the filetree 2012-04-21 22:26:15 -07:00
Paul Kulchenko
4dcf470c09 Fixed an issue with menu search to insert a new item 2012-04-20 22:25:14 -07:00
Paul Kulchenko
fcdbd456de Added code analyzer based on lua-inspect 2012-04-20 14:47:31 -07:00
Paul Kulchenko
e92127d6c8 Added luainspect library (https://github.com/davidm/lua-inspect) 2012-04-20 14:36:38 -07:00
Paul Kulchenko
0d8e6b0581 Added LuaSec (including binary Windows package from http://www.inf.puc-rio.br/~brunoos/luasec/) to provide SSL/HTTPS support for the socket library (needed for gist/github integration) 2012-04-19 18:17:48 -07:00
Paul Kulchenko
16d72396b1 Added missing mime/code.dll and reorganized socket module files (socket.*) to load correctly with require 2012-04-07 22:40:55 -07:00
Paul Kulchenko
6c7e289f71 Merge pull request #1 from dkulchenko/master
Format README with Markdown
2012-04-07 22:26:03 -07:00
Daniil Kulchenko
deb99ec084 Formatting improvements. 2012-03-27 14:08:36 -07:00
Daniil Kulchenko
12f84d3509 Switch README to Markdown. 2012-03-27 13:58:19 -07:00
40 changed files with 4758 additions and 262 deletions

View File

@@ -1,8 +1,10 @@
# Project Description
A simple and extensible Lua IDE and debugger. It supports multiple file
formats, "api" for autocompletion and tooltips, and custom command-line
tools. Its main focus is extensibility for target applications using Lua.
--[[ FEATURES ]]-----------------------------------------------------------
## Features
* Written in Lua, so easily customizable
* Automatically loads several 'plugin' like classes
@@ -23,23 +25,27 @@ tools. Its main focus is extensibility for target applications using Lua.
* Console to directly test code snippets with local and remote execution
* Integrated debugger (with support for local and remote debugging)
--[[ FRONT-ENDS ]]--------------------------------------------------------
## Frontends
There is currently two front-ends using the same editor engine. The original
is "estrela", which has a focus on 3d graphics related usage of Lua, especially
in combination with the luxinia engine or luxinia2 framework.
The second is "zbstudio" which has a focus on remote use of Lua in robotics.
one is `Estrela`, which has a focus on 3d graphics related usage of Lua,
especially in combination with the luxinia engine or luxinia2 framework.
The second front-end is `ZeroBrane Studio` (zbstudio) which has a focus
on using Lua in education, mobile development, and robotics.
Both are part of the standard distribution.
--[[ INSTALLATION ]]-------------------------------------------------------
## Installation
git clone git://github.com/pkulchenko/ZeroBraneStudio.git zbstudio
```bash
$ git clone git://github.com/pkulchenko/ZeroBraneStudio.git zbstudio
or
git clone git://estrelaeditor.git.sourceforge.net/gitroot/estrelaeditor/estrelaeditor estrelaeditor
$ git clone git://estrelaeditor.git.sourceforge.net/gitroot/estrelaeditor/estrelaeditor estrelaeditor
```
--[[ USAGE ]]--------------------------------------------------------------
## Usage
```
Open File(s):
<exe> <filename> [<filename>...]
any non-option will be treated as filename
@@ -47,17 +53,18 @@ Open File(s):
Overriding Config:
<exe> [...] -cfg "<luacode overriding config>" [...]
e.g.: zbstudio.exe -cfg "singleinstance=false;" somefile.lua
```
--[[ AUTHOR ]]-------------------------------------------------------------
## Author
Estrela Editor
### Estrela Editor
Luxinia Dev: Christoph Kubisch (crazybutcher@luxinia.de)
**Luxinia Dev:** Christoph Kubisch (crazybutcher@luxinia.de)
ZeroBrane Studio and MobDebug
### ZeroBrane Studio and MobDebug
ZeroBrane LLC: Paul Kulchenko (paul@kulchenko.com)
**ZeroBrane LLC:** Paul Kulchenko (paul@kulchenko.com)
--[[ LICENSE ]]------------------------------------------------------------
## License
See LICENSE file
See LICENSE file.

BIN
bin/clibs/mime/core.dll Normal file

Binary file not shown.

BIN
bin/clibs/ssl.dll Normal file

Binary file not shown.

View File

@@ -8,10 +8,11 @@ return {
local script
if rundebug then
DebuggerAttachDefault()
script = (""..
script = (
"package.path=package.path..';"..mainpath.."lualibs/?/?.lua;"..mainpath.."lualibs/?.lua';"..
"package.cpath=package.cpath..';"..mainpath.."bin/clibs/?.dll';"..
"require('mobdebug').loop('" .. wx.wxGetHostName().."',"..ide.debugger.portnumber..")")
rundebug
)
else
script = ([[dofile '%s']]):format(filepath)
end

929
lualibs/luainspect/ast.lua Normal file
View File

@@ -0,0 +1,929 @@
-- luainspect.ast - Lua Abstract Syntax Tree (AST) and token list operations.
--
-- Two main structures are maintained. A Metalua-style AST represents the
-- nested syntactic structure obtained from the parse.
-- A separate linear ordered list of tokens represents the syntactic structure
-- from the lexing, including line information (character positions only not row/columns),
-- comments, and keywords, which is originally built from the lineinfo attributes
-- injected by Metalua into the AST (IMPROVE: it probably would be simpler
-- to obtain this from the lexer directly rather then inferring it from the parsing).
-- During AST manipulations, the lineinfo maintained in the AST is ignored
-- because it was found more difficult to maintain and not in the optimal format.
--
-- The contained code deals with
-- - Building the AST from source.
-- - Building the tokenlist from the AST lineinfo.
-- - Querying the AST+tokenlist.
-- - Modifying the AST+tokenlist (including incremental parsing source -> AST)
-- - Annotating the AST with navigational info (e.g. parent links) to assist queries.
-- - Dumping the tokenlist for debugging.
--
-- (c) 2010 David Manura, MIT License.
--! require 'luainspect.typecheck' (context)
-- boilerplate/utility
-- LUA_PATH="?.lua;/path/to/metalua/src/compiler/?.lua;/path/to/metalua/src/lib/?.lua"
-- import modules -- order is important
require "lexer"
require "gg"
require "mlp_lexer"
require "mlp_misc"
require "mlp_table"
require "mlp_meta"
require "mlp_expr"
require "mlp_stat"
--require "mlp_ext"
_G.mlc = {} -- make gg happy
-- Metalua:IMPROVE: make above imports simpler
local M = {}
--[=TESTSUITE
-- utilities
local ops = {}
ops['=='] = function(a,b) return a == b end
local function check(opname, a, b)
local op = assert(ops[opname])
if not op(a,b) then
error("fail == " .. tostring(a) .. " " .. tostring(b))
end
end
--]=]
-- CATEGORY: debug
local function DEBUG(...)
if LUAINSPECT_DEBUG then
print('DEBUG:', ...)
end
end
-- Converts character position to row,column position in string src.
-- Add values are 1-indexed.
function M.pos_to_linecol(pos, src)
local linenum = 1
local lasteolpos = 0
for eolpos in src:gmatch"()\n" do
if eolpos > pos then break end
linenum = linenum + 1
lasteolpos = eolpos
end
local colnum = pos - lasteolpos
return linenum, colnum
end
-- Removes any sheband ("#!") line from Lua source string.
-- CATEGORY: Lua parsing
function M.remove_shebang(src)
local shebang = src:match("^#![^\r\n]*")
return shebang and (" "):rep(#shebang) .. src:sub(#shebang+1) or src
end
-- Custom version of loadstring that parses out line number info
-- CATEGORY: Lua parsing
function M.loadstring(src)
local f, err = loadstring(src, "")
if f then
return f
else
err = err:gsub('^%[string ""%]:', "")
local linenum = assert(err:match("(%d+):"))
local colnum = 0
local linenum2 = err:match("^%d+: '[^']+' expected %(to close '[^']+' at line (%d+)")
return nil, err, linenum, colnum, linenum2
end
end
-- helper for ast_from_string. Raises on error.
-- FIX? filename currently ignored in Metalua
-- CATEGORY: Lua parsing
local function ast_from_string_helper(src, filename)
filename = filename or '(string)'
local lx = mlp.lexer:newstream (src, filename)
local ast = mlp.chunk(lx)
return ast
end
-- Counts number of lines in text.
-- Warning: the decision of whether to count a trailing new-line in a file
-- or an empty file as a line is a little subjective. This function currently
-- defines the line count as 1 plus the number of new line characters.
-- CATEGORY: utility/string
local function linecount(text)
local n = 1
for _ in text:gmatch'\n' do
n = n + 1
end
return n
end
-- Converts Lua source string to Lua AST (via mlp/gg).
-- CATEGORY: Lua parsing
function M.ast_from_string(src, filename)
local ok, ast = pcall(ast_from_string_helper, src, filename)
if not ok then
local err = ast
err = err:match('[^\n]*')
err = err:gsub("^.-:%s*line", "line")
-- mlp.chunk prepending this is undesirable. error(msg,0) would be better in gg.lua. Reported.
-- TODO-Metalua: remove when fixed in Metalua.
local linenum, colnum = err:match("line (%d+), char (%d+)")
if not linenum then
-- Metalua libraries may return "...gg.lua:56: .../mlp_misc.lua:179: End-of-file expected"
-- without the normal line/char numbers given things like "if x then end end". Should be
-- fixed probably with gg.parse_error in _chunk in mlp_misc.lua.
-- TODO-Metalua: remove when fixed in Metalua.
linenum = linecount(src)
colnum = 1
end
local linenum2 = nil
return nil, err, linenum, colnum, linenum2
else
return ast
end
end
-- Simple comment parser. Returns Metalua-style comment.
-- CATEGORY: Lua lexing
local function quick_parse_comment(src)
local s = src:match"^%-%-([^\n]*)()\n$"
if s then return {s, 1, #src, 'short'} end
local _, s = src:match(lexer.lexer.patterns.long_comment .. '\r?\n?$')
if s then return {s, 1, #src, 'long'} end
return nil
end
--FIX:check new-line correctness
--note: currently requiring \n at end of single line comment to avoid
-- incremental compilation with `--x\nf()` and removing \n from still
-- recognizing as comment `--x`.
-- currently allowing \r\n at end of long comment since Metalua includes
-- it in lineinfo of long comment (FIX:Metalua?)
-- Gets length of longest prefix string in both provided strings.
-- Returns max n such that text1:sub(1,n) == text2:sub(1,n) and n <= max(#text1,#text2)
-- CATEGORY: string utility
local function longest_prefix(text1, text2)
local nmin = 0
local nmax = math.min(#text1, #text2)
while nmax > nmin do
local nmid = math.ceil((nmin+nmax)/2)
if text1:sub(1,nmid) == text2:sub(1,nmid) then
nmin = nmid
else
nmax = nmid-1
end
end
return nmin
end
-- Gets length of longest postfix string in both provided strings.
-- Returns max n such that text1:sub(-n) == text2:sub(-n) and n <= max(#text1,#text2)
-- CATEGORY: string utility
local function longest_postfix(text1, text2)
local nmin = 0
local nmax = math.min(#text1, #text2)
while nmax > nmin do
local nmid = math.ceil((nmin+nmax)/2)
if text1:sub(-nmid) == text2:sub(-nmid) then --[*]
nmin = nmid
else
nmax = nmid-1
end
end
return nmin
end -- differs from longest_prefix only on line [*]
-- Determines AST node that must be re-evaluated upon changing code string from
-- `src` to `bsrc`, given previous top_ast/tokenlist/src.
-- Note: decorates top_ast as side-effect.
-- If preserve is true, then does not expand AST match even if replacement is invalid.
-- CATEGORY: AST/tokenlist manipulation
function M.invalidated_code(top_ast, tokenlist, src, bsrc, preserve)
-- Converts posiiton range in src to position range in bsrc.
local function range_transform(src_fpos, src_lpos)
local src_nlpos = #src - src_lpos
local bsrc_fpos = src_fpos
local bsrc_lpos = #bsrc - src_nlpos
return bsrc_fpos, bsrc_lpos
end
if src == bsrc then return end -- up-to-date
-- Find range of positions in src that differences correspond to.
-- Note: for zero byte range, src_pos2 = src_pos1 - 1.
local npre = longest_prefix(src, bsrc)
local npost = math.min(#src-npre, longest_postfix(src, bsrc))
-- note: min avoids overlap ambiguity
local src_fpos, src_lpos = 1 + npre, #src - npost
-- Find smallest AST node containing src range above. May also
-- be contained in (smaller) comment or whitespace.
local match_ast, match_comment, iswhitespace =
M.smallest_ast_containing_range(top_ast, tokenlist, src_fpos, src_lpos)
DEBUG('invalidate-smallest:', match_ast and (match_ast.tag or 'notag'), match_comment, iswhitespace)
-- Determine which (ast, comment, or whitespace) to match, and get its pos range in src and bsrc.
local srcm_fpos, srcm_lpos, bsrcm_fpos, bsrcm_lpos, mast, mtype
if iswhitespace then
mast, mtype = nil, 'whitespace'
srcm_fpos, srcm_lpos = src_fpos, src_lpos
elseif match_comment then
mast, mtype = match_comment, 'comment'
srcm_fpos, srcm_lpos = match_comment.fpos, match_comment.lpos
else
mast, mtype = match_ast, 'ast'
repeat
srcm_fpos, srcm_lpos = M.ast_pos_range(mast, tokenlist)
if not srcm_fpos then
if mast == top_ast then
srcm_fpos, srcm_lpos = 1, #src
break
else
M.ensure_parents_marked(top_ast)
mast = mast.parent
end
end
until srcm_fpos
end
bsrcm_fpos, bsrcm_lpos = range_transform(srcm_fpos, srcm_lpos)
-- Never expand match if preserve specified.
if preserve then
return srcm_fpos, srcm_lpos, bsrcm_fpos, bsrcm_lpos, mast, mtype
end
-- Determine if replacement could break parent nodes.
local isreplacesafe
if mtype == 'whitespace' then
if bsrc:sub(bsrcm_fpos, bsrcm_lpos):match'^%s*$' then -- replaced with whitespace
if bsrc:sub(bsrcm_fpos-1, bsrcm_lpos+1):match'%s' then -- not eliminating whitespace
isreplacesafe = true
end
end
elseif mtype == 'comment' then
local m2src = bsrc:sub(bsrcm_fpos, bsrcm_lpos)
DEBUG('invalidate-comment[' .. m2src .. ']')
if quick_parse_comment(m2src) then -- replaced with comment
isreplacesafe = true
end
end
if isreplacesafe then -- return on safe replacement
return srcm_fpos, srcm_lpos, bsrcm_fpos, bsrcm_lpos, mast, mtype
end
-- Find smallest containing statement block that will compile (or top_ast).
while 1 do
match_ast = M.get_containing_statementblock(match_ast, top_ast)
if match_ast == top_ast then
return 1,#src, 1, #bsrc, match_ast, 'statblock'
-- entire AST invalidated
end
local srcm_fpos, srcm_lpos = M.ast_pos_range(match_ast, tokenlist)
local bsrcm_fpos, bsrcm_lpos = range_transform(srcm_fpos, srcm_lpos)
local msrc = bsrc:sub(bsrcm_fpos, bsrcm_lpos)
DEBUG('invalidate-statblock:', match_ast and match_ast.tag, '[' .. msrc .. ']')
if loadstring(msrc) then -- compiled
return srcm_fpos, srcm_lpos, bsrcm_fpos, bsrcm_lpos, match_ast, 'statblock'
end
M.ensure_parents_marked(top_ast)
match_ast = match_ast.parent
end
end
-- Walks AST `ast` in arbitrary order, visiting each node `n`, executing `fdown(n)` (if specified)
-- when doing down and `fup(n)` (if specified) when going if.
-- CATEGORY: AST walk
function M.walk(ast, fdown, fup)
assert(type(ast) == 'table')
if fdown then fdown(ast) end
for _,bast in ipairs(ast) do
if type(bast) == 'table' then
M.walk(bast, fdown, fup)
end
end
if fup then fup(ast) end
end
-- Replaces contents of table t1 with contents of table t2.
-- Does not change metatable (if any).
-- This function is useful for swapping one AST node with another
-- while preserving any references to the node.
-- CATEGORY: table utility
function M.switchtable(t1, t2)
for k in pairs(t1) do t1[k] = nil end
for k in pairs(t2) do t1[k] = t2[k] end
end
-- Inserts all elements in list bt at index i in list t.
-- CATEGORY: table utility
local function tinsertlist(t, i, bt)
local oldtlen, delta = #t, i - 1
for ti = #t + 1, #t + #bt do t[ti] = false end -- preallocate (avoid holes)
for ti = oldtlen, i, -1 do t[ti + #bt] = t[ti] end -- shift
for bi = 1, #bt do t[bi + delta] = bt[bi] end -- fill
end
--[=[TESTSUITE:
local function _tinsertlist(t, i, bt)
for bi=#bt,1,-1 do table.insert(t, i, bt[bi]) end
end -- equivalent but MUCH less efficient for large tables
local function _tinsertlist(t, i, bt)
for bi=1,#bt do table.insert(t, i+bi-1, bt[bi]) end
end -- equivalent but MUCH less efficient for large tables
local t = {}; tinsertlist(t, 1, {}); assert(table.concat(t)=='')
local t = {}; tinsertlist(t, 1, {2,3}); assert(table.concat(t)=='23')
local t = {4}; tinsertlist(t, 1, {2,3}); assert(table.concat(t)=='234')
local t = {2}; tinsertlist(t, 2, {3,4}); assert(table.concat(t)=='234')
local t = {4,5}; tinsertlist(t, 1, {2,3}); assert(table.concat(t)=='2345')
local t = {2,5}; tinsertlist(t, 2, {3,4}); assert(table.concat(t)=='2345')
local t = {2,3}; tinsertlist(t, 3, {4,5}); assert(table.concat(t)=='2345')
print 'DONE'
--]=]
-- Gets list of keyword positions related to node ast in source src
-- note: ast must be visible, i.e. have lineinfo (e.g. unlike `Id "self" definition).
-- Note: includes operators.
-- Note: Assumes ast Metalua-style lineinfo is valid.
-- CATEGORY: tokenlist build
function M.get_keywords(ast, src)
local list = {}
if not ast.lineinfo then return list end
-- examine space between each pair of children i and j.
-- special cases: 0 is before first child and #ast+1 is after last child
-- Put children in lexical order.
-- Some binary operations have arguments reversed from lexical order.
-- For example, `a > b` becomes `Op{'lt', `Id 'b', `Id 'a'}
local oast =
(ast.tag == 'Op' and #ast == 3 and ast[2].lineinfo.first[3] > ast[3].lineinfo.first[3])
and {ast[1], ast[3], ast[2]} or ast
local i = 0
while i <= #ast do
-- j is node following i that has lineinfo
local j = i+1; while j < #ast+1 and not oast[j].lineinfo do j=j+1 end
-- Get position range [fpos,lpos] between subsequent children.
local fpos
if i == 0 then -- before first child
fpos = ast.lineinfo.first[3]
else
local last = oast[i].lineinfo.last; local c = last.comments
fpos = (c and #c > 0 and c[#c][3] or last[3]) + 1
end
local lpos
if j == #ast+1 then -- after last child
lpos = ast.lineinfo.last[3]
else
local first = oast[j].lineinfo.first; local c = first.comments
--DEBUG('first', ast.tag, first[3], src:sub(first[3], first[3]+3))
lpos = (c and #c > 0 and c[1][2] or first[3]) - 1
end
-- Find keyword in range.
local spos = fpos
repeat
local mfpos, tok, mlppos = src:match("^%s*()(%a+)()", spos)
if not mfpos then
mfpos, tok, mlppos = src:match("^%s*()(%p+)()", spos)
end
if mfpos then
local mlpos = mlppos-1
if mlpos > lpos then mlpos = lpos end
--DEBUG('look', ast.tag, #ast,i,j,'*', mfpos, tok, mlppos, fpos, lpos, src:sub(fpos, fpos+5))
if mlpos >= mfpos then
list[#list+1] = mfpos
list[#list+1] = mlpos
end
end
spos = mlppos
until not spos or spos > lpos
-- note: finds single keyword. in `local function` returns only `local`
--DEBUG(i,j ,'test[' .. src:sub(fpos, lpos) .. ']')
i = j -- next
--DESIGN:Lua: comment: string.match accepts a start position but not a stop position
end
return list
end
-- Q:Metalua: does ast.lineinfo[loc].comments imply #ast.lineinfo[loc].comments > 0 ?
-- Generates ordered list of tokens in top_ast/src.
-- Note: currently ignores operators and parens.
-- Note: Modifies ast.
-- Note: Assumes ast Metalua-style lineinfo is valid.
-- CATEGORY: AST/tokenlist query
local isterminal = {Nil=true, Dots=true, True=true, False=true, Number=true, String=true,
Dots=true, Id=true}
local function compare_tokens_(atoken, btoken) return atoken.fpos < btoken.fpos end
function M.ast_to_tokenlist(top_ast, src)
local tokens = {} -- {nbytes=#src}
local isseen = {}
M.walk(top_ast, function(ast)
if isterminal[ast.tag] then -- Extract terminal
local token = ast
if ast.lineinfo then
token.fpos, token.lpos, token.ast = ast.lineinfo.first[3], ast.lineinfo.last[3], ast
table.insert(tokens, token)
end
else -- Extract non-terminal
local keywordposlist = M.get_keywords(ast, src)
for i=1,#keywordposlist,2 do
local fpos, lpos = keywordposlist[i], keywordposlist[i+1]
local toksrc = src:sub(fpos, lpos)
local token = {tag='Keyword', fpos=fpos, lpos=lpos, ast=ast, toksrc}
table.insert(tokens, token)
end
end
-- Extract comments
for i=1,2 do
local comments = ast.lineinfo and ast.lineinfo[i==1 and 'first' or 'last'].comments
if comments then for _, comment in ipairs(comments) do
if not isseen[comment] then
comment.tag = 'Comment'
local token = comment
token.fpos, token.lpos, token.ast = comment[2], comment[3], comment
table.insert(tokens, token)
isseen[comment] = true
end
end end
end
end, nil)
table.sort(tokens, compare_tokens_)
return tokens
end
-- Gets tokenlist range [fidx,lidx] covered by ast. Returns nil,nil if not found.
--FIX:PERFORMANCE:this is slow on large files.
-- CATEGORY: AST/tokenlist query
function M.ast_idx_range_in_tokenlist(tokenlist, ast)
-- Get list of primary nodes under ast.
local isold = {}; M.walk(ast, function(ast) isold[ast] = true end)
-- Get range.
local fidx, lidx
for idx=1,#tokenlist do
local token = tokenlist[idx]
if isold[token.ast] then
lidx = idx
if not fidx then fidx = idx end
end
end
return fidx, lidx
end
-- Gets index range in tokenlist overlapped by character position range [fpos, lpos].
-- For example, `do ff() end` with range ` ff() ` would match tokens `ff()`.
-- Tokens partly inside range are counted, so range `f()` would match tokens `ff()`.
-- If lidx = fidx - 1, then position range is whitespace between tokens lidx (on left)
-- and fidx (on right), and this may include token pseudoindices 0 (start of file) and
-- #tokenlist+1 (end of file).
-- Note: lpos == fpos - 1 indicates zero-width range between chars lpos and fpos.
-- CATEGORY: tokenlist query
function M.tokenlist_idx_range_over_pos_range(tokenlist, fpos, lpos)
-- Find first/last indices of tokens overlapped (even partly) by position range.
local fidx, lidx
for idx=1,#tokenlist do
local token = tokenlist[idx]
--if (token.fpos >= fpos and token.fpos <= lpos) or (token.lpos >= fpos and token.lpos <= lpos) then -- token overlaps range
if fpos <= token.lpos and lpos >= token.fpos then -- range overlaps token (even partially)
if not fidx then fidx = idx end
lidx = idx
end
end
if not fidx then -- on fail, check between tokens
for idx=1,#tokenlist+1 do -- between idx-1 and idx
local tokfpos, toklpos = tokenlist[idx-1] and tokenlist[idx-1].lpos, tokenlist[idx] and tokenlist[idx].fpos
if (not tokfpos or fpos > tokfpos) and (not toklpos or lpos < toklpos) then -- range between tokens
return idx, idx-1
end
end
end
return fidx, lidx
end
--[=[TESTSUITE
local function test(...)
return table.concat({M.tokenlist_idx_range_over_pos_range(...)}, ',')
end
check('==', test({}, 2, 2), "1,0") -- no tokens
check('==', test({{tag='Id', fpos=1, lpos=1}}, 2, 2), "2,1") -- right of one token
check('==', test({{tag='Id', fpos=3, lpos=3}}, 2, 2), "1,0") -- left of one token
check('==', test({{tag='Id', fpos=3, lpos=4}}, 2, 3), "1,1") -- left partial overlap one token
check('==', test({{tag='Id', fpos=3, lpos=4}}, 4, 5), "1,1") -- right partial overlap one token
check('==', test({{tag='Id', fpos=3, lpos=6}}, 4, 5), "1,1") -- partial inner overlap one token
check('==', test({{tag='Id', fpos=3, lpos=6}}, 3, 6), "1,1") -- exact overlap one token
check('==', test({{tag='Id', fpos=4, lpos=5}}, 3, 6), "1,1") -- extra overlap one token
check('==', test({{tag='Id', fpos=2, lpos=3}, {tag='Id', fpos=5, lpos=6}}, 4, 4), "2,1") -- between tokens, " " exact
check('==', test({{tag='Id', fpos=2, lpos=3}, {tag='Id', fpos=5, lpos=6}}, 4, 3), "2,1") -- between tokens, "" on left
check('==', test({{tag='Id', fpos=2, lpos=3}, {tag='Id', fpos=5, lpos=6}}, 5, 4), "2,1") -- between tokens, "" on right
check('==', test({{tag='Id', fpos=2, lpos=3}, {tag='Id', fpos=4, lpos=5}}, 4, 3), "2,1") -- between tokens, "" exact
--]=]
-- Removes tokens in tokenlist covered by ast.
-- CATEGORY: tokenlist manipulation
local function remove_ast_in_tokenlist(tokenlist, ast)
local fidx, lidx = M.ast_idx_range_in_tokenlist(tokenlist, ast)
if fidx then -- note: fidx implies lidx
for idx=lidx,fidx,-1 do table.remove(tokenlist, idx) end
end
end
-- Inserts tokens from btokenlist into tokenlist. Preserves sort.
-- CATEGORY: tokenlist manipulation
local function insert_tokenlist(tokenlist, btokenlist)
local ftoken = btokenlist[1]
if ftoken then
-- Get index in tokenlist in which to insert tokens in btokenlist.
local fidx
for idx=1,#tokenlist do
if tokenlist[idx].fpos > ftoken.fpos then fidx = idx; break end
end
fidx = fidx or #tokenlist + 1 -- else append
-- Insert tokens.
tinsertlist(tokenlist, fidx, btokenlist)
end
end
-- Get character position range covered by ast in tokenlist. Returns nil,nil on not found.
-- CATEGORY: AST/tokenlist query
function M.ast_pos_range(ast, tokenlist) -- IMPROVE:style: ast_idx_range_in_tokenlist has params reversed
local fidx, lidx = M.ast_idx_range_in_tokenlist(tokenlist, ast)
if fidx then
return tokenlist[fidx].fpos, tokenlist[lidx].lpos
else
return nil, nil
end
end
-- Gets string representation of AST node. nil if none.
-- IMPROVE: what if node is empty block?
-- CATEGORY: AST/tokenlist query
function M.ast_to_text(ast, tokenlist, src) -- IMPROVE:style: ast_idx_range_in_tokenlist has params reversed
local fpos, lpos = M.ast_pos_range(ast, tokenlist)
if fpos then
return src:sub(fpos, lpos)
else
return nil
end
end
-- Gets smallest AST node in top_ast/tokenlist/src
-- completely containing position range [pos1, pos2].
-- careful: "function" is not part of the `Function node.
-- If range is inside comment, returns comment also.
-- If range is inside whitespace, then returns true in third return value.
-- CATEGORY: AST/tokenlist query
function M.smallest_ast_containing_range(top_ast, tokenlist, pos1, pos2)
local f0idx, l0idx = M.tokenlist_idx_range_over_pos_range(tokenlist, pos1, pos2)
-- Find enclosing AST.
M.ensure_parents_marked(top_ast)
local fidx, lidx = f0idx, l0idx
while tokenlist[fidx] and not tokenlist[fidx].ast.parent do fidx = fidx - 1 end
while tokenlist[lidx] and not tokenlist[lidx].ast.parent do lidx = lidx + 1 end
-- DEBUG(fidx, lidx, f0idx, l0idx, #tokenlist, pos1, pos2, tokenlist[fidx], tokenlist[lidx])
local ast = not (tokenlist[fidx] and tokenlist[lidx]) and top_ast or
M.common_ast_parent(tokenlist[fidx].ast, tokenlist[lidx].ast, top_ast)
-- DEBUG('m2', tokenlist[fidx], tokenlist[lidx], top_ast, ast, ast and ast.tag)
if l0idx == f0idx - 1 then -- whitespace
return ast, nil, true
elseif l0idx == f0idx and tokenlist[l0idx].tag == 'Comment' then
return ast, tokenlist[l0idx], nil
else
return ast, nil, nil
end
end
--IMPROVE: handle string edits and maybe others
-- Gets smallest statement block containing position pos or
-- nearest statement block before pos, whichever is smaller, given ast/tokenlist.
function M.current_statementblock(ast, tokenlist, pos)
local fidx,lidx = M.tokenlist_idx_range_over_pos_range(tokenlist, pos, pos)
if fidx > lidx then fidx = lidx end -- use nearest backward
-- Find closest AST node backward
while fidx >= 1 and tokenlist[fidx].tag == 'Comment' do fidx=fidx-1 end
if fidx < 1 then return ast, false end
local mast = tokenlist[fidx].ast
if not mast then return ast, false end
mast = M.get_containing_statementblock(mast, ast)
local isafter = false
if mast.tag2 ~= 'Block' then
local mfidx,mlidx = M.ast_idx_range_in_tokenlist(tokenlist, mast)
if pos > mlidx then
isafter = true
end
end
return mast, isafter
end
-- Gets index of bast in ast (nil if not found).
-- CATEGORY: AST query
function M.ast_idx(ast, bast)
for idx=1,#ast do
if ast[idx] == bast then return idx end
end
return nil
end
-- Gets parent of ast and index of ast in parent.
-- Root node top_ast must also be provided. Returns nil, nil if ast is root.
-- Note: may call mark_parents.
-- CATEGORY: AST query
function M.ast_parent_idx(top_ast, ast)
if ast == top_ast then return nil, nil end
M.ensure_parents_marked(top_ast); assert(ast.parent)
local idx = M.ast_idx(ast.parent, ast)
return ast.parent, idx
end
-- Gets common parent of aast and bast. Always returns value.
-- Must provide root top_ast too.
-- CATEGORY: AST query
function M.common_ast_parent(aast, bast, top_ast)
M.ensure_parents_marked(top_ast)
local isparent = {}
local tast = bast; repeat isparent[tast] = true; tast = tast.parent until not tast
local uast = aast; repeat if isparent[uast] then return uast end; uast = uast.parent until not uast
assert(false)
end
-- Replaces old_ast with new_ast/new_tokenlist in top_ast/tokenlist.
-- Note: assumes new_ast is a block. assumes old_ast is a statement or block.
-- CATEGORY: AST/tokenlist
function M.replace_statements(top_ast, tokenlist, old_ast, new_ast, new_tokenlist)
remove_ast_in_tokenlist(tokenlist, old_ast)
insert_tokenlist(tokenlist, new_tokenlist)
if old_ast == top_ast then -- special case: no parent
M.switchtable(old_ast, new_ast) -- note: safe since block is not in tokenlist.
else
local parent_ast, idx = M.ast_parent_idx(top_ast, old_ast)
table.remove(parent_ast, idx)
tinsertlist(parent_ast, idx, new_ast)
end
-- fixup annotations
for _,bast in ipairs(new_ast) do
if top_ast.tag2 then M.mark_tag2(bast, bast.tag == 'Do' and 'StatBlock' or 'Block') end
if old_ast.parent then M.mark_parents(bast, old_ast.parent) end
end
end
-- Adjusts lineinfo in tokenlist.
-- All char positions starting at pos1 are shifted by delta number of chars.
-- CATEGORY: tokenlist
function M.adjust_lineinfo(tokenlist, pos1, delta)
for _,token in ipairs(tokenlist) do
if token.fpos >= pos1 then
token.fpos = token.fpos + delta
end
if token.lpos >= pos1 then
token.lpos = token.lpos + delta
end
end
--tokenlist.nbytes = tokenlist.nbytes + delta
end
-- For each node n in ast, sets n.parent to parent node of n.
-- Assumes ast.parent will be parent_ast (may be nil)
-- CATEGORY: AST query
function M.mark_parents(ast, parent_ast)
ast.parent = parent_ast
for _,ast2 in ipairs(ast) do
if type(ast2) == 'table' then
M.mark_parents(ast2, ast)
end
end
end
-- Calls mark_parents(ast) if ast not marked.
-- CATEGORY: AST query
function M.ensure_parents_marked(ast)
if ast[1] and not ast[1].parent then M.mark_parents(ast) end
end
-- For each node n in ast, sets n.tag2 to context string:
-- 'Block' - node is block
-- 'Stat' - node is statement
-- 'StatBlock' - node is statement and block (i.e. `Do)
-- 'Exp' - node is expression
-- 'Explist' - node is expression list (or identifier list)
-- 'Pair' - node is key-value pair in table constructor
-- note: ast.tag2 will be set to context.
-- CATEGORY: AST query
local iscertainstat = {Do=true, Set=true, While=true, Repeat=true, If=true,
Fornum=true, Forin=true, Local=true, Localrec=true, Return=true, Break=true}
function M.mark_tag2(ast, context)
context = context or 'Block'
ast.tag2 = context
for i,bast in ipairs(ast) do
if type(bast) == 'table' then
local nextcontext
if bast.tag == 'Do' then
nextcontext = 'StatBlock'
elseif iscertainstat[bast.tag] then
nextcontext = 'Stat'
elseif bast.tag == 'Call' or bast.tag == 'Invoke' then
nextcontext = context == 'Block' and 'Stat' or 'Exp'
--DESIGN:Metalua: these calls actually contain expression lists,
-- but the expression list is not represented as a complete node
-- by Metalua (as blocks are in `Do statements)
elseif bast.tag == 'Pair' then
nextcontext = 'Pair'
elseif not bast.tag then
if ast.tag == 'Set' or ast.tag == 'Local' or ast.tag == 'Localrec'
or ast.tag == 'Forin' and i <= 2
or ast.tag == 'Function' and i == 1
then
nextcontext = 'Explist'
else
nextcontext = 'Block'
end
else
nextcontext = 'Exp'
end
M.mark_tag2(bast, nextcontext)
end
end
end
-- Gets smallest statement or block containing or being `ast`.
-- The AST root node `top_ast` must also be provided.
-- Note: may decorate AST as side-effect (mark_tag2/mark_parents).
-- top_ast is assumed a block, so this is always successful.
-- CATEGORY: AST query
function M.get_containing_statementblock(ast, top_ast)
if not top_ast.tag2 then M.mark_tag2(top_ast) end
if ast.tag2 == 'Stat' or ast.tag2 == 'StatBlock' or ast.tag2 == 'Block' then
return ast
else
M.ensure_parents_marked(top_ast)
return M.get_containing_statementblock(ast.parent, top_ast)
end
end
-- Finds smallest statement, block, or comment AST in ast/tokenlist containing position
-- range [fpos, lpos]. If allowexpand is true (default nil) and located AST
-- coincides with position range, then next containing statement is used
-- instead (this allows multiple calls to further expand the statement selection).
-- CATEGORY: AST query
function M.select_statementblockcomment(ast, tokenlist, fpos, lpos, allowexpand)
--IMPROVE: rename ast to top_ast
local match_ast, comment_ast = M.smallest_ast_containing_range(ast, tokenlist, fpos, lpos)
local select_ast = comment_ast or M.get_containing_statementblock(match_ast, ast)
local nfpos, nlpos = M.ast_pos_range(select_ast, tokenlist)
--DEBUG('s', nfpos, nlpos, fpos, lpos, match_ast.tag, select_ast.tag)
if allowexpand and fpos == nfpos and lpos == nlpos then
if comment_ast then
-- Select enclosing statement.
select_ast = match_ast
nfpos, nlpos = M.ast_pos_range(select_ast, tokenlist)
else
-- note: multiple times may be needed to expand selection. For example, in
-- `for x=1,2 do f() end` both the statement `f()` and block `f()` have
-- the same position range.
M.ensure_parents_marked(ast)
while select_ast.parent and fpos == nfpos and lpos == nlpos do
select_ast = M.get_containing_statementblock(select_ast.parent, ast)
nfpos, nlpos = M.ast_pos_range(select_ast, tokenlist)
end
end
end
return nfpos, nlpos
end
-- Converts tokenlist to string representation for debugging.
-- CATEGORY: tokenlist debug
function M.dump_tokenlist(tokenlist)
local ts = {}
for i,token in ipairs(tokenlist) do
ts[#ts+1] = 'tok.' .. i .. ': [' .. token.fpos .. ',' .. token.lpos .. '] '
.. tostring(token[1]) .. ' ' .. tostring(token.ast.tag)
end
return table.concat(ts, '\n') -- .. 'nbytes=' .. tokenlist.nbytes .. '\n'
end
--FIX:Q: does this handle Unicode ok?
--FIX?:Metalua: fails on string with escape sequence '\/'. The Reference Manual
-- doesn't say this sequence is valid though.
--FIX:Metalua: In `local --[[x]] function --[[y]] f() end`,
-- 'x' comment omitted from AST.
--FIX:Metalua: `do --[[x]] end` doesn't generate comments in AST.
-- `if x then --[[x]] end` and `while 1 do --[[x]] end` generates
-- comments in first/last of block
--FIX:Metalua: `--[[x]] f() --[[y]]` returns lineinfo around `f()`.
-- `--[[x]] --[[y]]` returns lineinfo around everything.
--FIX:Metalua: `while 1 do --[[x]] --[[y]] end` returns first > last
-- lineinfo for contained block
--FIX:Metalua: search for "PATCHED:LuaInspect" in the metalualib folder.
--FIX?:Metalua: loadstring parses "--x" but metalua omits the comment in the AST
--FIX?:Metalua: `local x` is generating `Local{{`Id{x}}, {}}`, which
-- has no lineinfo on {}. This is contrary to the Metalua
-- spec: `Local{ {ident+} {expr+}? }.
-- Other things like `self` also generate no lineinfo.
-- The ast2.lineinfo above avoids this.
--FIX:Metalua: Metalua shouldn't overwrite ipairs/pairs. Note: Metalua version
-- doesn't set errorlevel correctly.
--Q:Metalua: Why does `return --[[y]] z --[[x]]` have
-- lineinfo.first.comments, lineinfo.last.comments,
-- plus lineinfo.comments (which is the same as lineinfo.first.comments) ?
--CAUTION:Metalua: `do f() end` returns lineinfo around `do f() end`, while
-- `while 1 do f() end` returns lineinfo around `f()` for inner block.
--CAUTION:Metalua: The lineinfo on Metalua comments is inconsistent with other
-- nodes
--CAUTION:Metalua: lineinfo of table in `f{}` is [3,2], of `f{ x,y }` it's [4,6].
-- This is inconsistent with `x={}` which is [3,4] and `f""` which is [1,2]
-- for the string.
--CAUTION:Metalua: only the `function()` form of `Function includes `function`
-- in lineinfo. 'function' is part of `Localrec and `Set in syntactic sugar form.
--[=[TESTSUITE
-- test longest_prefix/longest_postfix
local function pr(text1, text2)
local lastv
local function same(v)
assert(not lastv or v == lastv); lastv = v; return v
end
local function test1(text1, text2) -- test prefix/postfix
same(longest_prefix(text1, text2))
same(longest_postfix(text1:reverse(), text2:reverse()))
end
local function test2(text1, text2) -- test swap
test1(text1, text2)
test1(text2, text1)
end
for _,extra in ipairs{"", "x", "xy", "xyz"} do -- test extra chars
test2(text1, text2..extra)
test2(text2, text1..extra)
end
return lastv
end
check('==', pr("",""), 0)
check('==', pr("a",""), 0)
check('==', pr("a","a"), 1)
check('==', pr("ab",""), 0)
check('==', pr("ab","a"), 1)
check('==', pr("ab","ab"), 2)
check('==', pr("abcdefg","abcdefgh"), 7)
--]=]
--[=[TESTSUITE
print 'DONE'
--]=]
return M

View File

@@ -0,0 +1,390 @@
--[[
compat_env v$(_VERSION) - Lua 5.1/5.2 environment compatibility functions
SYNOPSIS
-- Get load/loadfile compatibility functions only if using 5.1.
local CL = pcall(load, '') and _G or require 'compat_env'
local load = CL.load
local loadfile = CL.loadfile
-- The following now works in both Lua 5.1 and 5.2:
assert(load('return 2*pi', nil, 't', {pi=math.pi}))()
assert(loadfile('ex.lua', 't', {print=print}))()
-- Get getfenv/setfenv compatibility functions only if using 5.2.
local getfenv = _G.getfenv or require 'compat_env'.getfenv
local setfenv = _G.setfenv or require 'compat_env'.setfenv
local function f() return x end
setfenv(f, {x=2})
print(x, getfenv(f).x) --> 2, 2
DESCRIPTION
This module provides Lua 5.1/5.2 environment related compatibility functions.
This includes implementations of Lua 5.2 style `load` and `loadfile`
for use in Lua 5.1. It also includes Lua 5.1 style `getfenv` and `setfenv`
for use in Lua 5.2.
API
local CL = require 'compat_env'
CL.load (ld [, source [, mode [, env] ] ]) --> f [, err]
This behaves the same as the Lua 5.2 `load` in both
Lua 5.1 and 5.2.
http://www.lua.org/manual/5.2/manual.html#pdf-load
CL.loadfile ([filename [, mode [, env] ] ]) --> f [, err]
This behaves the same as the Lua 5.2 `loadfile` in both
Lua 5.1 and 5.2.
http://www.lua.org/manual/5.2/manual.html#pdf-loadfile
CL.getfenv ([f]) --> t
This is identical to the Lua 5.1 `getfenv` in Lua 5.1.
This behaves similar to the Lua 5.1 `getfenv` in Lua 5.2.
When a global environment is to be returned, or when `f` is a
C function, this returns `_G` since Lua 5.2 doesn't have
(thread) global and C function environments. This will also
return `_G` if the Lua function `f` lacks an `_ENV`
upvalue, but it will raise an error if uncertain due to lack of
debug info. It is not normally considered good design to use
this function; when possible, use `load` or `loadfile` instead.
http://www.lua.org/manual/5.1/manual.html#pdf-getfenv
CL.setfenv (f, t)
This is identical to the Lua 5.1 `setfenv` in Lua 5.1.
This behaves similar to the Lua 5.1 `setfenv` in Lua 5.2.
This will do nothing if `f` is a Lua function that
lacks an `_ENV` upvalue, but it will raise an error if uncertain
due to lack of debug info. See also Design Notes below.
It is not normally considered good design to use
this function; when possible, use `load` or `loadfile` instead.
http://www.lua.org/manual/5.1/manual.html#pdf-setfenv
DESIGN NOTES
This module intends to provide robust and fairly complete reimplementations
of the environment related Lua 5.1 and Lua 5.2 functions.
No effort is made, however, to simulate rare or difficult to simulate features,
such as thread environments, although this is liable to change in the future.
Such 5.1 capabilities are discouraged and ideally
removed from 5.1 code, thereby allowing your code to work in both 5.1 and 5.2.
In Lua 5.2, a `setfenv(f, {})`, where `f` lacks any upvalues, will be silently
ignored since there is no `_ENV` in this function to write to, and the
environment will have no effect inside the function anyway. However,
this does mean that `getfenv(setfenv(f, t))` does not necessarily equal `t`,
which is incompatible with 5.1 code (a possible workaround would be [1]).
If `setfenv(f, {})` has an upvalue but no debug info, then this will raise
an error to prevent inadvertently executing potentially untrusted code in the
global environment.
It is not normally considered good design to use `setfenv` and `getfenv`
(one reason they were removed in 5.2). When possible, consider replacing
these with `load` or `loadfile`, which are more restrictive and have native
implementations in 5.2.
This module might be merged into a more general Lua 5.1/5.2 compatibility
library (e.g. a full reimplementation of Lua 5.2 `_G`). However,
`load/loadfile/getfenv/setfenv` perhaps are among the more cumbersome
functions not to have.
INSTALLATION
Download compat_env.lua:
wget https://raw.github.com/gist/1654007/compat_env.lua
Copy compat_env.lua into your LUA_PATH.
Alternately, unpack, test, and install into LuaRocks:
wget https://raw.github.com/gist/1422205/sourceunpack.lua
lua sourceunpack.lua compat_env.lua
(cd out && luarocks make)
Related work
http://lua-users.org/wiki/LuaVersionCompatibility
https://github.com/stevedonovan/Penlight/blob/master/lua/pl/utils.lua
- penlight implementations of getfenv/setfenv
http://lua-users.org/lists/lua-l/2010-06/msg00313.html
- initial getfenv/setfenv implementation
References
[1] http://lua-users.org/lists/lua-l/2010-06/msg00315.html
Copyright
(c) 2012 David Manura. Licensed under the same terms as Lua 5.1/5.2 (MIT license).
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
--]]---------------------------------------------------------------------
local M = {_TYPE='module', _NAME='compat_env', _VERSION='0.2.20120124'}
local function check_chunk_type(s, mode)
local nmode = mode or 'bt'
local is_binary = s and #s > 0 and s:byte(1) == 27
if is_binary and not nmode:match'b' then
return nil, ("attempt to load a binary chunk (mode is '%s')"):format(mode)
elseif not is_binary and not nmode:match't' then
return nil, ("attempt to load a text chunk (mode is '%s')"):format(mode)
end
return true
end
local IS_52_LOAD = pcall(load, '')
if IS_52_LOAD then
M.load = _G.load
M.loadfile = _G.loadfile
else
-- 5.2 style `load` implemented in 5.1
function M.load(ld, source, mode, env)
local f
if type(ld) == 'string' then
local s = ld
local ok, err = check_chunk_type(s, mode); if not ok then return ok, err end
local err; f, err = loadstring(s, source); if not f then return f, err end
elseif type(ld) == 'function' then
local ld2 = ld
if (mode or 'bt') ~= 'bt' then
local first = ld()
local ok, err = check_chunk_type(first, mode); if not ok then return ok, err end
ld2 = function()
if first then
local chunk=first; first=nil; return chunk
else return ld() end
end
end
local err; f, err = load(ld2, source); if not f then return f, err end
else
error(("bad argument #1 to 'load' (function expected, got %s)"):format(type(ld)), 2)
end
if env then setfenv(f, env) end
return f
end
-- 5.2 style `loadfile` implemented in 5.1
function M.loadfile(filename, mode, env)
if (mode or 'bt') ~= 'bt' then
local ioerr
local fh, err = io.open(filename, 'rb'); if not fh then return fh, err end
local function ld() local chunk; chunk,ioerr = fh:read(4096); return chunk end
local f, err = M.load(ld, filename and '@'..filename, mode, env)
fh:close()
if not f then return f, err end
if ioerr then return nil, ioerr end
return f
else
local f, err = loadfile(filename); if not f then return f, err end
if env then setfenv(f, env) end
return f
end
end
end
if _G.setfenv then -- Lua 5.1
M.setfenv = _G.setfenv
M.getfenv = _G.getfenv
else -- >= Lua 5.2
-- helper function for `getfenv`/`setfenv`
local function envlookup(f)
local name, val
local up = 0
local unknown
repeat
up=up+1; name, val = debug.getupvalue(f, up)
if name == '' then unknown = true end
until name == '_ENV' or name == nil
if name ~= '_ENV' then
up = nil
if unknown then error("upvalues not readable in Lua 5.2 when debug info missing", 3) end
end
return (name == '_ENV') and up, val, unknown
end
-- helper function for `getfenv`/`setfenv`
local function envhelper(f, name)
if type(f) == 'number' then
if f < 0 then
error(("bad argument #1 to '%s' (level must be non-negative)"):format(name), 3)
elseif f < 1 then
error("thread environments unsupported in Lua 5.2", 3) --[*]
end
f = debug.getinfo(f+2, 'f').func
elseif type(f) ~= 'function' then
error(("bad argument #1 to '%s' (number expected, got %s)"):format(type(name, f)), 2)
end
return f
end
-- [*] might simulate with table keyed by coroutine.running()
-- 5.1 style `setfenv` implemented in 5.2
function M.setfenv(f, t)
local f = envhelper(f, 'setfenv')
local up, val, unknown = envlookup(f)
if up then
debug.upvaluejoin(f, up, function() return up end, 1) -- unique upvalue [*]
debug.setupvalue(f, up, t)
else
local what = debug.getinfo(f, 'S').what
if what ~= 'Lua' and what ~= 'main' then -- not Lua func
error("'setfenv' cannot change environment of given object", 2)
end -- else ignore no _ENV upvalue (warning: incompatible with 5.1)
end
end
-- [*] http://lua-users.org/lists/lua-l/2010-06/msg00313.html
-- 5.1 style `getfenv` implemented in 5.2
function M.getfenv(f)
if f == 0 or f == nil then return _G end -- simulated behavior
local f = envhelper(f, 'setfenv')
local up, val = envlookup(f)
if not up then return _G end -- simulated behavior [**]
return val
end
-- [**] possible reasons: no _ENV upvalue, C function
end
return M
--[[ FILE rockspec.in
package = 'compat_env'
version = '$(_VERSION)-1'
source = {
url = 'https://raw.github.com/gist/1654007/$(GITID)/compat_env.lua',
--url = 'https://raw.github.com/gist/1654007/compat_env.lua', -- latest raw
--url = 'https://gist.github.com/gists/1654007/download',
md5 = '$(MD5)'
}
description = {
summary = 'Lua 5.1/5.2 environment compatibility functions',
detailed = [=[
Provides Lua 5.1/5.2 environment related compatibility functions.
This includes implementations of Lua 5.2 style `load` and `loadfile`
for use in Lua 5.1. It also includes Lua 5.1 style `getfenv` and `setfenv`
for use in Lua 5.2.
]=],
license = 'MIT/X11',
homepage = 'https://gist.github.com/1654007',
maintainer = 'David Manura'
}
dependencies = {} -- Lua 5.1 or 5.2
build = {
type = 'builtin',
modules = {
['compat_env'] = 'compat_env.lua'
}
}
--]]---------------------------------------------------------------------
--[[ FILE test.lua
-- test.lua - test suite for compat_env module.
local CL = require 'compat_env'
local load = CL.load
local loadfile = CL.loadfile
local setfenv = CL.setfenv
local getfenv = CL.getfenv
local function checkeq(a, b, e)
if a ~= b then error(
'not equal ['..tostring(a)..'] ['..tostring(b)..'] ['..tostring(e)..']')
end
end
local function checkerr(pat, ok, err)
assert(not ok, 'checkerr')
assert(type(err) == 'string' and err:match(pat), err)
end
-- test `load`
checkeq(load('return 2')(), 2)
checkerr('expected near', load'return 2 2')
checkerr('text chunk', load('return 2', nil, 'b'))
checkerr('text chunk', load('', nil, 'b'))
checkerr('binary chunk', load('\027', nil, 't'))
checkeq(load('return 2*x',nil,'bt',{x=5})(), 10)
checkeq(debug.getinfo(load('')).source, '')
checkeq(debug.getinfo(load('', 'foo')).source, 'foo')
-- test `loadfile`
local fh = assert(io.open('tmp.lua', 'wb'))
fh:write('return (...) or x')
fh:close()
checkeq(loadfile('tmp.lua')(2), 2)
checkeq(loadfile('tmp.lua', 't')(2), 2)
checkerr('text chunk', loadfile('tmp.lua', 'b'))
checkeq(loadfile('tmp.lua', nil, {x=3})(), 3)
checkeq(debug.getinfo(loadfile('tmp.lua')).source, '@tmp.lua')
checkeq(debug.getinfo(loadfile('tmp.lua', 't', {})).source, '@tmp.lua')
os.remove'tmp.lua'
-- test `setfenv`/`getfenv`
x = 5
local a,b=true; local function f(c) if a then return x,b,c end end
setfenv(f, {x=3})
checkeq(f(), 3)
checkeq(getfenv(f).x, 3)
checkerr('cannot change', pcall(setfenv, string.len, {})) -- C function
checkeq(getfenv(string.len), _G) -- C function
local function g()
setfenv(1, {x=4})
checkeq(getfenv(1).x, 4)
return x
end
checkeq(g(), 4) -- numeric level
if _G._VERSION ~= 'Lua 5.1' then
checkerr('unsupported', pcall(setfenv, 0, {}))
end
checkeq(getfenv(0), _G)
checkeq(getfenv(), _G) -- no arg
checkeq(x, 5) -- main unaltered
setfenv(function()end, {}) -- no upvalues, ignore
checkeq(getfenv(function()end), _G) -- no upvaluse
if _G._VERSION ~= 'Lua 5.1' then
checkeq(getfenv(setfenv(function()end, {})), _G) -- warning: incompatible with 5.1
end
x = nil
print 'OK'
--]]---------------------------------------------------------------------
--[[ FILE CHANGES.txt
0.2.20120124
Renamed module to compat_env (from compat_load)
Add getfenv/setfenv functions
0.1.20120121
Initial public release
--]]

View File

@@ -0,0 +1,90 @@
-- Recursive object dumper, for debugging.
-- (c) 2010 David Manura, MIT License.
local M = {}
-- My own object dumper.
-- Intended for debugging, not serialization, with compact formatting.
-- Robust against recursion.
-- Renders Metalua table tag fields specially {tag=X, ...} --> "`X{...}".
-- On first call, only pass parameter o.
-- CATEGORY: AST debug
local ignore_keys_ = {lineinfo=true}
local norecurse_keys_ = {parent=true, ast=true}
local function dumpstring_key_(k, isseen, newindent)
local ks = type(k) == 'string' and k:match'^[%a_][%w_]*$' and k or
'[' .. M.dumpstring(k, isseen, newindent) .. ']'
return ks
end
local function sort_keys_(a, b)
if type(a) == 'number' and type(b) == 'number' then
return a < b
elseif type(a) == 'number' then
return false
elseif type(b) == 'number' then
return true
elseif type(a) == 'string' and type(b) == 'string' then
return a < b
else
return tostring(a) < tostring(b) -- arbitrary
end
end
function M.dumpstring(o, isseen, indent, key)
isseen = isseen or {}
indent = indent or ''
if type(o) == 'table' then
if isseen[o] or norecurse_keys_[key] then
return (type(o.tag) == 'string' and '`' .. o.tag .. ':' or '') .. tostring(o)
else isseen[o] = true end -- avoid recursion
local used = {}
local tag = o.tag
local s = '{'
if type(o.tag) == 'string' then
s = '`' .. tag .. s; used['tag'] = true
end
local newindent = indent .. ' '
local ks = {}; for k in pairs(o) do ks[#ks+1] = k end
table.sort(ks, sort_keys_)
--for i,k in ipairs(ks) do print ('keys', k) end
local forcenummultiline
for k in pairs(o) do
if type(k) == 'number' and type(o[k]) == 'table' then forcenummultiline = true end
end
-- inline elements
for _,k in ipairs(ks) do
if used[k] then -- skip
elseif ignore_keys_[k] then used[k] = true
elseif (type(k) ~= 'number' or not forcenummultiline) and
type(k) ~= 'table' and (type(o[k]) ~= 'table' or norecurse_keys_[k])
then
s = s .. dumpstring_key_(k, isseen, newindent) .. '=' .. M.dumpstring(o[k], isseen, newindent, k) .. ', '
used[k] = true
end
end
-- elements on separate lines
local done
for _,k in ipairs(ks) do
if not used[k] then
if not done then s = s .. '\n'; done = true end
s = s .. newindent .. dumpstring_key_(k, isseen) .. '=' .. M.dumpstring(o[k], isseen, newindent, k) .. ',\n'
end
end
s = s:gsub(',(%s*)$', '%1')
s = s .. (done and indent or '') .. '}'
return s
elseif type(o) == 'string' then
return string.format('%q', o)
else
return tostring(o)
end
end
return M

View File

@@ -0,0 +1,222 @@
-- LuaInspect.globals - identifier scope analysis
-- Locates locals, globals, and their definitions.
--
-- (c) D.Manura, 2008-2010, MIT license.
-- based on http://lua-users.org/wiki/DetectingUndefinedVariables
local M = {}
--! require 'luainspect.typecheck' (context)
local LA = require "luainspect.ast"
local function definelocal(scope, name, ast)
if scope[name] then
scope[name].localmasked = true
ast.localmasking = scope[name]
end
scope[name] = ast
if name == '_' then ast.isignore = true end
end
-- Resolves scoping and usages of variable in AST.
-- Data Notes:
-- ast.localdefinition refers to lexically scoped definition of `Id node `ast`.
-- If ast.localdefinition == ast then ast is a "lexical definition".
-- If ast.localdefinition == nil, then variable is global.
-- ast.functionlevel is the number of functions the AST is contained in.
-- ast.functionlevel is defined iff ast is a lexical definition.
-- ast.isparam is true iff ast is a lexical definition and a function parameter.
-- ast.isset is true iff ast is a lexical definition and exists an assignment on it.
-- ast.isused is true iff ast is a lexical definition and has been referred to.
-- ast.isignore is true if local variable should be ignored (e.g. typically "_")
-- ast.localmasking - for a lexical definition, this is set to the lexical definition
-- this is masking (i.e. same name). nil if not masking.
-- ast.localmasked - true iff lexical definition masked by another lexical definition.
-- ast.isfield is true iff `String node ast is used for field access on object,
-- e.g. x.y or x['y'].z
-- ast.previous - For `Index{o,s} or `Invoke{o,s,...}, s.previous == o
local function traverse(ast, scope, globals, level, functionlevel)
scope = scope or {}
local blockrecurse
ast.level = level
-- operations on walking down the AST
if ast.tag == 'Local' then
blockrecurse = 1
-- note: apply new scope after processing values
elseif ast.tag == 'Localrec' then
local namelist_ast, valuelist_ast = ast[1], ast[2]
for _,value_ast in ipairs(namelist_ast) do
assert(value_ast.tag == 'Id')
local name = value_ast[1]
local parentscope = getmetatable(scope).__index
definelocal(parentscope, name, value_ast)
value_ast.localdefinition = value_ast
value_ast.functionlevel = functionlevel
value_ast.level = level+1
end
blockrecurse = 1
elseif ast.tag == 'Id' then
local name = ast[1]
if scope[name] then
ast.localdefinition = scope[name]
ast.functionlevel = functionlevel
scope[name].isused = true
else -- global, do nothing
end
elseif ast.tag == 'Function' then
local paramlist_ast, body_ast = ast[1], ast[2]
functionlevel = functionlevel + 1
for _,param_ast in ipairs(paramlist_ast) do
local name = param_ast[1]
assert(param_ast.tag == 'Id' or param_ast.tag == 'Dots')
if param_ast.tag == 'Id' then
definelocal(scope, name, param_ast)
param_ast.localdefinition = param_ast
param_ast.functionlevel = functionlevel
param_ast.isparam = true
end
param_ast.level = level+1
end
blockrecurse = 1
elseif ast.tag == 'Set' then
local reflist_ast, valuelist_ast = ast[1], ast[2]
for _,ref_ast in ipairs(reflist_ast) do
if ref_ast.tag == 'Id' then
local name = ref_ast[1]
if scope[name] then
scope[name].isset = true
else
if not globals[name] then
globals[name] = {set=ref_ast}
end
end
end
ref_ast.level = level+1
end
--ENHANCE? We could differentiate assignments to x (which indicates that
-- x is not const) and assignments to a member of x (which indicates that
-- x is not a pointer to const) and assignments to any nested member of x
-- (which indicates that x it not a transitive const).
elseif ast.tag == 'Fornum' then
blockrecurse = 1
elseif ast.tag == 'Forin' then
blockrecurse = 1
end
-- recurse (depth-first search down the AST)
if ast.tag == 'Repeat' then
local block_ast, cond_ast = ast[1], ast[2]
local scope = scope
for _,stat_ast in ipairs(block_ast) do
scope = setmetatable({}, {__index = scope})
traverse(stat_ast, scope, globals, level+1, functionlevel)
end
scope = setmetatable({}, {__index = scope})
traverse(cond_ast, scope, globals, level+1, functionlevel)
elseif ast.tag == 'Fornum' then
local name_ast, block_ast = ast[1], ast[#ast]
-- eval value list in current scope
for i=2, #ast-1 do traverse(ast[i], scope, globals, level+1, functionlevel) end
-- eval body in next scope
local name = name_ast[1]
definelocal(scope, name, name_ast)
name_ast.localdefinition = name_ast
name_ast.functionlevel = functionlevel
traverse(block_ast, scope, globals, level+1, functionlevel)
elseif ast.tag == 'Forin' then
local namelist_ast, vallist_ast, block_ast = ast[1], ast[2], ast[3]
-- eval value list in current scope
traverse(vallist_ast, scope, globals, level+1, functionlevel)
-- eval body in next scope
for _,name_ast in ipairs(namelist_ast) do
local name = name_ast[1]
definelocal(scope, name, name_ast)
name_ast.localdefinition = name_ast
name_ast.functionlevel = functionlevel
name_ast.level = level+1
end
traverse(block_ast, scope, globals, level+1, functionlevel)
else -- normal
for i,v in ipairs(ast) do
if i ~= blockrecurse and type(v) == 'table' then
local scope = setmetatable({}, {__index = scope})
traverse(v, scope, globals, level+1, functionlevel)
end
end
end
-- operations on walking up the AST
if ast.tag == 'Local' then
-- Unlike Localrec, variables come into scope after evaluating values.
local namelist_ast, valuelist_ast = ast[1], ast[2]
for _,name_ast in ipairs(namelist_ast) do
assert(name_ast.tag == 'Id')
local name = name_ast[1]
local parentscope = getmetatable(scope).__index
definelocal(parentscope, name, name_ast)
name_ast.localdefinition = name_ast
name_ast.functionlevel = functionlevel
name_ast.level = level+1
end
elseif ast.tag == 'Index' then
if ast[2].tag == 'String' then
ast[2].isfield = true
ast[2].previous = ast[1]
end
elseif ast.tag == 'Invoke' then
assert(ast[2].tag == 'String')
ast[2].isfield = true
ast[2].previous = ast[1]
end
end
function M.globals(ast)
-- Default list of defined variables.
local scope = setmetatable({}, {})
local globals = {}
traverse(ast, scope, globals, 1, 1) -- Start check.
return globals
end
-- Gets locals in scope of statement of block ast. If isafter is true and ast is statement,
-- uses scope just after statement ast.
-- Assumes 'parent' attributes on ast are marked.
-- Returns table mapping name -> AST local definition.
function M.variables_in_scope(ast, isafter)
local scope = {}
local cast = ast
while cast.parent do
local midx = LA.ast_idx(cast.parent, cast)
for idx=1,midx do
local bast = cast.parent[idx]
if bast.tag == 'Localrec' or bast.tag == 'Local' and (idx < midx or isafter) then
local names_ast = bast[1]
for bidx=1,#names_ast do
local name_ast = names_ast[bidx]
local name = name_ast[1]
scope[name] = name_ast
end
elseif cast ~= ast and (bast.tag == 'For' or bast.tag == 'Forin' or bast.tag == 'Function') then
local names_ast = bast[1]
for bidx=1,#names_ast do
local name_ast = names_ast[bidx]
if name_ast.tag == 'Id' then --Q: or maybe `Dots should be included
local name = name_ast[1]
scope[name] = name_ast
end
end
end
end
cast = cast.parent
end
return scope
end
return M

1431
lualibs/luainspect/init.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,433 @@
local M = {}
local T = require "luainspect.types"
-- signatures of known globals
M.global_signatures = {
assert = "assert (v [, message])",
collectgarbage = "collectgarbage (opt [, arg])",
dofile = "dofile (filename)",
error = "error (message [, level])",
_G = "(table)",
getfenv = "getfenv ([f])",
getmetatable = "getmetatable (object)",
ipairs = "ipairs (t)",
load = "load (func [, chunkname])",
loadfile = "loadfile ([filename])",
loadstring = "loadstring (string [, chunkname])",
next = "next (table [, index])",
pairs = "pairs (t)",
pcall = "pcall (f, arg1, ...)",
print = "print (...)",
rawequal = "rawequal (v1, v2)",
rawget = "rawget (table, index)",
rawset = "rawset (table, index, value)",
select = "select (index, ...)",
setfenv = "setfenv (f, table)",
setmetatable = "setmetatable (table, metatable)",
tonumber = "tonumber (e [, base])",
tostring = "tostring (e)",
type = "type (v)",
unpack = "unpack (list [, i [, j]])",
_VERSION = "(string)",
xpcall = "xpcall (f, err)",
module = "module (name [, ...])",
require = "require (modname)",
coroutine = "(table) coroutine manipulation library",
debug = "(table) debug facilities library",
io = "(table) I/O library",
math = "(table) math functions libary",
os = "(table) OS facilities library",
package = "(table) package library",
string = "(table) string manipulation library",
table = "(table) table manipulation library",
["coroutine.create"] = "coroutine.create (f)",
["coroutine.resume"] = "coroutine.resume (co [, val1, ...])",
["coroutine.running"] = "coroutine.running ()",
["coroutine.status"] = "coroutine.status (co)",
["coroutine.wrap"] = "coroutine.wrap (f)",
["coroutine.yield"] = "coroutine.yield (...)",
["debug.debug"] = "debug.debug ()",
["debug.getfenv"] = "debug.getfenv (o)",
["debug.gethook"] = "debug.gethook ([thread])",
["debug.getinfo"] = "debug.getinfo ([thread,] function [, what])",
["debug.getlocal"] = "debug.getlocal ([thread,] level, local)",
["debug.getmetatable"] = "debug.getmetatable (object)",
["debug.getregistry"] = "debug.getregistry ()",
["debug.getupvalue"] = "debug.getupvalue (func, up)",
["debug.setfenv"] = "debug.setfenv (object, table)",
["debug.sethook"] = "debug.sethook ([thread,] hook, mask [, count])",
["debug.setlocal"] = "debug.setlocal ([thread,] level, local, value)",
["debug.setmetatable"] = "debug.setmetatable (object, table)",
["debug.setupvalue"] = "debug.setupvalue (func, up, value)",
["debug.traceback"] = "debug.traceback ([thread,] [message] [, level])",
["io.close"] = "io.close ([file])",
["io.flush"] = "io.flush ()",
["io.input"] = "io.input ([file])",
["io.lines"] = "io.lines ([filename])",
["io.open"] = "io.open (filename [, mode])",
["io.output"] = "io.output ([file])",
["io.popen"] = "io.popen (prog [, mode])",
["io.read"] = "io.read (...)",
["io.tmpfile"] = "io.tmpfile ()",
["io.type"] = "io.type (obj)",
["io.write"] = "io.write (...)",
["math.abs"] = "math.abs (x)",
["math.acos"] = "math.acos (x)",
["math.asin"] = "math.asin (x)",
["math.atan"] = "math.atan (x)",
["math.atan2"] = "math.atan2 (y, x)",
["math.ceil"] = "math.ceil (x)",
["math.cos"] = "math.cos (x)",
["math.cosh"] = "math.cosh (x)",
["math.deg"] = "math.deg (x)",
["math.exp"] = "math.exp (x)",
["math.floor"] = "math.floor (x)",
["math.fmod"] = "math.fmod (x, y)",
["math.frexp"] = "math.frexp (x)",
["math.huge"] = "math.huge",
["math.ldexp"] = "math.ldexp (m, e)",
["math.log"] = "math.log (x)",
["math.log10"] = "math.log10 (x)",
["math.max"] = "math.max (x, ...)",
["math.min"] = "math.min (x, ...)",
["math.modf"] = "math.modf (x)",
["math.pi"] = "math.pi",
["math.pow"] = "math.pow (x, y)",
["math.rad"] = "math.rad (x)",
["math.random"] = "math.random ([m [, n]])",
["math.randomseed"] = "math.randomseed (x)",
["math.sin"] = "math.sin (x)",
["math.sinh"] = "math.sinh (x)",
["math.sqrt"] = "math.sqrt (x)",
["math.tan"] = "math.tan (x)",
["math.tanh"] = "math.tanh (x)",
["os.clock"] = "os.clock ()",
["os.date"] = "os.date ([format [, time]])",
["os.difftime"] = "os.difftime (t2, t1)",
["os.execute"] = "os.execute ([command])",
["os.exit"] = "os.exit ([code])",
["os.getenv"] = "os.getenv (varname)",
["os.remove"] = "os.remove (filename)",
["os.rename"] = "os.rename (oldname, newname)",
["os.setlocale"] = "os.setlocale (locale [, category])",
["os.time"] = "os.time ([table])",
["os.tmpname"] = "os.tmpname ()",
["package.cpath"] = "package.cpath",
["package.loaded"] = "package.loaded",
["package.loaders"] = "package.loaders",
["package.loadlib"] = "package.loadlib (libname, funcname)",
["package.path"] = "package.path",
["package.preload"] = "package.preload",
["package.seeall"] = "package.seeall (module)",
["string.byte"] = "string.byte (s [, i [, j]])",
["string.char"] = "string.char (...)",
["string.dump"] = "string.dump (function)",
["string.find"] = "string.find (s, pattern [, init [, plain]])",
["string.format"] = "string.format (formatstring, ...)",
["string.gmatch"] = "string.gmatch (s, pattern)",
["string.gsub"] = "string.gsub (s, pattern, repl [, n])",
["string.len"] = "string.len (s)",
["string.lower"] = "string.lower (s)",
["string.match"] = "string.match (s, pattern [, init])",
["string.rep"] = "string.rep (s, n)",
["string.reverse"] = "string.reverse (s)",
["string.sub"] = "string.sub (s, i [, j])",
["string.upper"] = "string.upper (s)",
["table.concat"] = "table.concat (table [, sep [, i [, j]]])",
["table.insert"] = "table.insert (table, [pos,] value)",
["table.maxn"] = "table.maxn (table)",
["table.remove"] = "table.remove (table [, pos])",
["table.sort"] = "table.sort (table [, comp])",
}
-- utility function. Converts e.g. name 'math.sqrt' to its value.
local function resolve_global_helper_(name)
local o = _G
for fieldname in name:gmatch'[^%.]+' do o = o[fieldname] end
return o
end
local function resolve_global(name)
local a, b = pcall(resolve_global_helper_, name)
if a then return b else return nil, b end
end
-- Same as global_signatures but maps value (not name) to signature.
M.value_signatures = {}
local isobject = {['function']=true, ['table']=true, ['userdata']=true, ['coroutine']=true}
for name,sig in pairs(M.global_signatures) do
local val, err = resolve_global(name)
if isobject[type(val)] then
M.value_signatures[val] = sig
end
end
-- min,max argument counts.
M.argument_counts = {
[assert] = {1,2},
[collectgarbage] = {1,2},
[dofile] = {1},
[error] = {1,2},
[getfenv or false] = {0,1},
[getmetatable] = {1,1},
[ipairs] = {1,1},
[load] = {1,2},
[loadfile] = {0,1},
[loadstring] = {1,2},
[next] = {1,2},
[pairs] = {1,1},
[pcall] = {1,math.huge},
[print] = {0,math.huge},
[rawequal] = {2,2},
[rawget] = {2,2},
[rawset] = {3,3},
[select] = {1, math.huge},
[setfenv or false] = {2,2},
[setmetatable] = {2,2},
[tonumber] = {1,2},
[tostring] = {1},
[type] = {1},
[unpack] = {1,3},
[xpcall] = {2,2},
[module] = {1,math.huge},
[require] = {1,1},
[coroutine.create] = {1,1},
[coroutine.resume] = {1, math.huge},
[coroutine.running] = {0,0},
[coroutine.status] = {1,1},
[coroutine.wrap] = {1,1},
[coroutine.yield] = {0,math.huge},
[debug.debug] = {0,0},
[debug.getfenv or false] = {1,1},
[debug.gethook] = {0,1},
[debug.getinfo] = {1,3},
[debug.getlocal] = {2,3},
[debug.getmetatable] = {1,1},
[debug.getregistry] = {0,0},
[debug.getupvalue] = {2,2},
[debug.setfenv or false] = {2,2},
[debug.sethook] = {2,4},
[debug.setlocal] = {3,4},
[debug.setmetatable] = {2,2},
[debug.setupvalue] = {3,3},
[debug.traceback] = {0,3},
[io.close] = {0,1},
[io.flush] = {0,0},
[io.input] = {0,1},
[io.lines] = {0,1},
[io.open] = {1,2},
[io.output] = {0,1},
[io.popen] = {1,2},
[io.read] = {0,math.huge},
[io.tmpfile] = {0},
[io.type] = {1},
[io.write] = {0,math.huge},
[math.abs] = {1},
[math.acos] = {1},
[math.asin] = {1},
[math.atan] = {1},
[math.atan2] = {2,2},
[math.ceil] = {1,1},
[math.cos] = {1,1},
[math.cosh] = {1,1},
[math.deg] = {1,1},
[math.exp] = {1,1},
[math.floor] = {1,1},
[math.fmod] = {2,2},
[math.frexp] = {1,1},
[math.ldexp] = {2,2},
[math.log] = {1,1},
[math.log10] = {1,1},
[math.max] = {1,math.huge},
[math.min] = {1,math.huge},
[math.modf] = {1,1},
[math.pow] = {2,2},
[math.rad] = {1,1},
[math.random] = {0,2},
[math.randomseed] = {1,1},
[math.sin] = {1,1},
[math.sinh] = {1,1},
[math.sqrt] = {1,1},
[math.tan] = {1,1},
[math.tanh] = {1,1},
[os.clock] = {0,0},
[os.date] = {0,2},
[os.difftime] = {2,2},
[os.execute] = {0,1},
[os.exit] = {0,1},
[os.getenv] = {1,1},
[os.remove] = {1,1},
[os.rename] = {2,2},
[os.setlocale] = {1,2},
[os.time] = {0,1},
[os.tmpname] = {0,0},
[package.loadlib] = {2,2},
[package.seeall] = {1,1},
[string.byte] = {1,3},
[string.char] = {0,math.huge},
[string.dump] = {1,1},
[string.find] = {2,4},
[string.format] = {1,math.huge},
[string.gmatch] = {2,2},
[string.gsub] = {3,4},
[string.len] = {1,1},
[string.lower] = {1,1},
[string.match] = {2,3},
[string.rep] = {2,2},
[string.reverse] = {1,1},
[string.sub] = {2,3},
[string.upper] = {1,1},
[table.concat] = {1,4},
[table.insert] = {2,3},
[table.maxn] = {1,1},
[table.remove] = {1,2},
[table.sort] = {1,2},
[false] = nil -- trick (relies on potentially undefined behavior)
}
-- functions with zero or nearly zero side-effects, and with deterministic results, that may be evaluated by the analyzer.
M.safe_function = {
[require] = true,
[rawequal] = true,
[rawget] = true,
[require] = true, -- sort of
[select] = true,
[tonumber] = true,
[tostring] = true,
[type] = true,
[unpack] = true,
[coroutine.create] = true,
-- [coroutine.resume]
[coroutine.running] = true,
[coroutine.status] = true,
[coroutine.wrap] = true,
--[coroutine.yield]
-- [debug.debug]
--[debug.getfenv] = true,
[debug.gethook] = true,
[debug.getinfo] = true,
[debug.getlocal] = true,
[debug.getmetatable] = true,
[debug.getregistry] = true,
[debug.getupvalue] = true,
-- [debug.setfenv]
-- [debug.sethook]
-- [debug.setlocal]
-- [debug.setmetatable]
-- [debug.setupvalue]
-- [debug.traceback] = true,
[io.type] = true,
-- skip all other io.*
[math.abs] = true,
[math.acos] = true,
[math.asin] = true,
[math.atan] = true,
[math.atan2] = true,
[math.ceil] = true,
[math.cos] = true,
[math.cosh] = true,
[math.deg] = true,
[math.exp] = true,
[math.floor] = true,
[math.fmod] = true,
[math.frexp] = true,
[math.ldexp] = true,
[math.log] = true,
[math.log10] = true,
[math.max] = true,
[math.min] = true,
[math.modf] = true,
[math.pow] = true,
[math.rad] = true,
--[math.random]
--[math.randomseed]
[math.sin] = true,
[math.sinh] = true,
[math.sqrt] = true,
[math.tan] = true,
[math.tanh] = true,
[os.clock] = true, -- safe but non-deterministic
[os.date] = true,-- safe but non-deterministic
[os.difftime] = true,
--[os.execute]
--[os.exit]
[os.getenv] = true, -- though depends on environment
--[os.remove]
--[os.rename]
--[os.setlocale]
[os.time] = true, -- safe but non-deterministic
--[os.tmpname]
[string.byte] = true,
[string.char] = true,
[string.dump] = true,
[string.find] = true,
[string.format] = true,
[string.gmatch] = true,
[string.gsub] = true,
[string.len] = true,
[string.lower] = true,
[string.match] = true,
[string.rep] = true,
[string.reverse] = true,
[string.sub] = true,
[string.upper] = true,
[table.maxn] = true,
}
M.mock_functions = {}
-- TODO:IMPROVE
local function mockfunction(func, ...)
local inputs = {n=0}
local outputs = {n=0}
local isoutputs
for i=1,select('#', ...) do
local v = select(i, ...)
if type(v) == 'table' then v = v[1] end
if v == 'N' or v == 'I' then v = T.number end
if v == '->' then
isoutputs = true
elseif isoutputs then
outputs[#outputs+1] = v; outputs.n = outputs.n + 1
else
inputs[#inputs+1] = v; inputs.n = inputs.n + 1
end
end
M.mock_functions[func] = {inputs=inputs, outputs=outputs}
end
mockfunction(math.abs, 'N', '->', {'N',0,math.huge})
mockfunction(math.acos, {'N',-1,1}, '->', {'N',0,math.pi/2})
mockfunction(math.asin, {'N',-1,1}, '->', {'N',-math.pi/2,math.pi/2})
mockfunction(math.atan, {'N',-math.huge,math.huge}, '->',
{'N',-math.pi/2,math.pi/2})
--FIX atan2
mockfunction(math.ceil, 'N','->','I')
mockfunction(math.cos, 'N','->',{'N',-1,1})
mockfunction(math.cosh, 'N','->',{'N',1,math.huge})
mockfunction(math.deg, 'N','->','N')
mockfunction(math.exp, 'N','->',{'N',0,math.huge})
mockfunction(math.floor, 'N','->','I')
mockfunction(math.fmod, 'N','N','->','N')
mockfunction(math.frexp, 'N','->',{'N',-1,1},'->','I')
mockfunction(math.ldexp, {'N','I'},'->','N')
mockfunction(math.log, {'N',0,math.huge},'->','N')
mockfunction(math.log10, {'N',0,math.huge},'->','N')
-- function max(...) print 'NOT IMPL'end
-- function min(...) print 'NOT IMPL'end
mockfunction(math.modf, 'N','->','I',{'N',-1,1})
mockfunction(math.pow, 'N','N','->','N') -- improve?
mockfunction(math.rad, 'N','->','N')
-- random = function() print 'NOT IMPL' end
mockfunction(math.randomseed, 'N')
mockfunction(math.sin, 'N','->',{'N',-1,1})
mockfunction(math.sinh, 'N','->','N')
mockfunction(math.sqrt, {'N',0,math.huge},'->',{'N',0,math.huge})
mockfunction(math.tan, 'N','->','N') -- improve?
mockfunction(math.tanh, 'N','->',{'N',-1,1})
return M

View File

@@ -0,0 +1,40 @@
-- luainspect.typecheck - Type definitions used to check LuaInspect itself.
--
-- (c) 2010 David Manura, MIT License.
local T = require "luainspect.types"
local ast_mt = {__tostring = function(s) return 'AST' end}
return function(context)
-- AST type.
local ast = T.table {
tag = T.string,
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},
ast=T.table{comments=T.table{T.table{T.string,T.number,T.number}},T.number,T.number,T.number,T.string}},
isfield=T.boolean, tag2=T.string,
value=T.universal, valueself=T.number, valuelist=T.table{n=T.number, isvaluepegged=T.boolean},
resolvedname=T.string, definedglobal=T.boolean, id=T.number, isparam=T.boolean, isset=T.boolean, isused=T.boolean,
isignore=T.boolean,
functionlevel=T.number, localmasked=T.boolean, note=T.string, nocollect=T.table{}, isdead=T.boolean}
-- FIX: some of these are "boolean or nil" actually
ast.localdefinition=ast; ast.localmasking = ast
ast.previous = ast; ast.parent = ast
ast.seevalue = ast; ast.seenote=ast
setmetatable(ast, ast_mt)
ast[1] = ast; ast[2] = ast
context.apply_value('ast$', ast)
-- Token type.
context.apply_value('token$', T.table{
tag=T.string, fpos=T.number, lpos=T.number, keywordid=T.number, ast=ast, [1]=T.string
})
-- Lua source code string type.
context.apply_value('src$', '')
-- SciTE syler object type.
local nf = function()end
context.apply_value('^styler$', T.table{SetState=nf, More=nf, Current=nf, Forward=nf, StartStyling=nf, EndStyling=nf, language=T.string})
end

View File

@@ -0,0 +1,130 @@
local T = {} -- types
-- istype[o] iff o represents a type (i.e. set of values)
T.istype = {}
-- iserror[o] iff o represents an error type (created via T.error).
T.iserror = {}
-- istabletype[o] iff o represents a table type (created by T.table).
T.istabletype = {}
-- Number type
T.number = {}
setmetatable(T.number, T.number)
function T.number.__tostring(self)
return 'number'
end
T.istype[T.number] = true
-- String type
T.string = {}
setmetatable(T.string, T.string)
function T.string.__tostring(self)
return 'string'
end
T.istype[T.string] = true
-- Boolean type
T.boolean = {}
setmetatable(T.boolean, T.boolean)
function T.boolean.__tostring(self)
return 'boolean'
end
T.istype[T.boolean] = true
-- Table type
function T.table(t)
T.istype[t] = true
T.istabletype[t] = true
return t
end
-- Universal type. This is a superset of all other types.
T.universal = {}
setmetatable(T.universal, T.universal)
function T.universal.__tostring(self)
return 'unknown'
end
T.istype[T.universal] = true
-- nil type. Represents `nil` but can be stored in tables.
T['nil'] = {}
setmetatable(T['nil'], T['nil'])
T['nil'].__tostring = function(self)
return 'nil'
end
T.istype[T['nil']] = true
-- None type. Represents a non-existent value, in a similar way
-- that `none` is used differently from `nil` in the Lua C API.
T.none = {}
setmetatable(T.none, T.none)
function T.none.__tostring(self)
return 'none'
end
T.istype[T.none] = true
-- Error type
local CError = {}; CError.__index = CError
function CError.__tostring(self) return "error:" .. tostring(self.value) end
function T.error(val)
local self = setmetatable({value=val}, CError)
T.istype[self] = true
T.iserror[self] = true
return self
end
-- Gets a type that is a superset of the two given types.
function T.superset_types(a, b)
if T.iserror[a] then return a end
if T.iserror[b] then return b end
if rawequal(a, b) then -- note: including nil == nil
return a
elseif type(a) == 'string' or a == T.string then
if type(b) == 'string' or b == T.string then
return T.string
else
return T.universal
end
elseif type(a) == 'number' or a == T.number then
if type(b) == 'number' or b == T.number then
return T.number
else
return T.universal
end
elseif type(a) == 'boolean' or a == T.boolean then
if type(b) == 'boolean' or b == T.boolean then
return T.boolean
else
return T.universal
end
else
return T.universal -- IMPROVE
end
end
--[[TESTS:
assert(T.superset_types(2, 2) == 2)
assert(T.superset_types(2, 3) == T.number)
assert(T.superset_types(2, T.number) == T.number)
assert(T.superset_types(T.number, T.string) == T.universal)
print 'DONE'
--]]
-- Determines whether type `o` certainly evaluates to true (true),
-- certainly evaluates to false (false) or could evaluate to either
-- true of false ('?').
function T.boolean_cast(o)
if T.iserror[o] then -- special case
return '?'
elseif o == nil or o == false or o == T['nil'] then -- all subsets of {nil, false}
return false
elseif o == T.universal or o == T.boolean then -- all supersets of boolean
return '?'
else -- all subsets of universal - {nil, false}
return true
end
end
return T

View File

@@ -1,14 +1,15 @@
--
-- MobDebug 0.44
-- MobDebug 0.449
-- Copyright Paul Kulchenko 2011-2012
-- Based on RemDebug 1.0 (http://www.keplerproject.org/remdebug)
-- Based on RemDebug 1.0 Copyright Kepler Project 2005
-- (http://www.keplerproject.org/remdebug)
--
local mobdebug = {
_NAME = "mobdebug",
_COPYRIGHT = "Paul Kulchenko",
_DESCRIPTION = "Mobile Remote Debugger for the Lua programming language",
_VERSION = "0.44"
_VERSION = "0.449"
}
local coroutine = coroutine
@@ -27,7 +28,13 @@ local mosync = mosync
-- this is a socket class that implements maConnect interface
local function socketMobileLua()
local self = {}
self.select = function() return {} end
self.select = function(readfrom) -- writeto and timeout parameters are ignored
local canread = {}
for _,s in ipairs(readfrom) do
if s:receive(0) then canread[s] = true end
end
return canread
end
self.connect = coroutine.wrap(function(host, port)
while true do
local connection = mosync.maConnect("socket://" .. host .. ":" .. port)
@@ -73,7 +80,7 @@ local function socketMobileLua()
end
return s
end
self.send = coroutine.wrap(function(self, msg)
self.send = coroutine.wrap(function(self, msg)
while true do
local numberOfBytes = stringToBuffer(msg, outBuffer)
mosync.maConnWrite(connection, outBuffer, numberOfBytes)
@@ -92,23 +99,27 @@ local function socketMobileLua()
self, msg = coroutine.yield()
end
end)
self.receive = coroutine.wrap(function(self, len)
self.receive = coroutine.wrap(function(self, len)
while true do
local line = recvBuffer
while (len and string.len(line) < len) -- either we need len bytes
or (not len and not line:find("\n")) do -- or one line (if no len specified)
or (not len and not line:find("\n")) -- or one line (if no len specified)
or (len == 0) do -- only check for new data (select-like)
mosync.maConnRead(connection, inBuffer, 1000)
while true do
mosync.maWait(0)
if len ~= 0 then mosync.maWait(0) end
mosync.maGetEvent(event)
local eventType = mosync.SysEventGetType(event)
if (mosync.EVENT_TYPE_CLOSE == eventType) then mosync.maExit(0) end
if (mosync.EVENT_TYPE_CONN == eventType and
mosync.SysEventGetConnHandle(event) == connection and
mosync.SysEventGetConnOpType(event) == mosync.CONNOP_READ) then
local result = mosync.SysEventGetConnResult(event);
local result = mosync.SysEventGetConnResult(event)
if result > 0 then line = line .. bufferToString(inBuffer, result) end
break; -- got the event we wanted; now check if we have all we need
if len == 0 then self, len = coroutine.yield("") end
break -- got the event we wanted; now check if we have all we need
elseif len == 0 then
self, len = coroutine.yield(nil)
end
end
end
@@ -142,14 +153,9 @@ end
local socket = mosync and socketMobileLua() or (require "socket")
--
-- RemDebug 1.0 Beta
-- Copyright Kepler Project 2005 (http://www.keplerproject.org/remdebug)
--
local debug = require "debug"
local coro_debugger
local events = { BREAK = 1, WATCH = 2 }
local events = { BREAK = 1, WATCH = 2, RESTART = 3 }
local breakpoints = {}
local watches = {}
local lastsource
@@ -157,15 +163,18 @@ local lastfile
local watchescnt = 0
local abort -- default value is nil; this is used in start/loop distinction
local check_break = false
local skip
local skipcount = 0
local step_into = false
local step_over = false
local step_level = 0
local stack_level = 0
local server
local deferror = "execution aborted at default debugee"
local debugee = function ()
local a = 1
a = a + 1
return "ok"
for _ = 1, 10 do a = a + 1 end
error(deferror)
end
local function set_breakpoint(file, line)
@@ -195,7 +204,7 @@ local function restore_vars(vars)
while true do
local name = debug.getlocal(3, i)
if not name then break end
debug.setlocal(3, i, vars[name])
if string.sub(name, 1, 1) ~= '(' then debug.setlocal(3, i, vars[name]) end
written_vars[name] = true
i = i + 1
end
@@ -204,7 +213,7 @@ local function restore_vars(vars)
local name = debug.getupvalue(func, i)
if not name then break end
if not written_vars[name] then
debug.setupvalue(func, i, vars[name])
if string.sub(name, 1, 1) ~= '(' then debug.setupvalue(func, i, vars[name]) end
written_vars[name] = true
end
i = i + 1
@@ -218,27 +227,62 @@ local function capture_vars()
while true do
local name, value = debug.getupvalue(func, i)
if not name then break end
vars[name] = value
if string.sub(name, 1, 1) ~= '(' then vars[name] = value end
i = i + 1
end
i = 1
while true do
local name, value = debug.getlocal(3, i)
if not name then break end
vars[name] = value
if string.sub(name, 1, 1) ~= '(' then vars[name] = value end
i = i + 1
end
setmetatable(vars, { __index = getfenv(func), __newindex = getfenv(func) })
return vars
end
local function stack_depth(start_depth)
for i = start_depth, 0, -1 do
if debug.getinfo(i, "l") then return i+1 end
end
return start_depth
end
local function is_safe(stack_level, conservative)
-- the stack grows up: 0 is getinfo, 1 is is_safe, 2 is debug_hook, 3 is user function
if stack_level == 3 then return true end
local main = debug.getinfo(3, "S").source
for i = 3, stack_level do
-- return if it is not safe to abort
local info = debug.getinfo(i, "S")
if not info then return true end
if conservative and info.source ~= main or info.what == "C" then return false end
end
return true
end
local function debug_hook(event, line)
if abort then error("aborted") end -- abort execution for RE/LOAD
if event == "call" then
stack_level = stack_level + 1
elseif event == "return" or event == "tail return" then
stack_level = stack_level - 1
elseif event == "line" then
-- check if we need to skip some callbacks (to save time)
if skip then
skipcount = skipcount + 1
if skipcount < skip or not is_safe(stack_level) then return end
skipcount = 0
end
-- this is needed to check if the stack got shorter.
-- this may happen when "pcall(load, '')" is called
-- or when "error()" is called in a function.
-- in either case there are more "call" than "return" events reported.
-- this validation is done for every "line" event, but should be
-- "cheap" as it only checks for the stack to get shorter
stack_level = stack_depth(stack_level)
local caller = debug.getinfo(2, "S")
-- grab the filename and fix it if needed
@@ -265,45 +309,58 @@ local function debug_hook(event, line)
local status, res = pcall(value)
if status and res then
coroutine.resume(coro_debugger, events.WATCH, vars, file, line, index)
restore_vars(vars)
end
end
end
if step_into
local suspend = (check_break
-- stack check is at least two times faster than select
-- 1.2s vs 2.5s for 100,000 iterations on 1.6Ghz CPU
and is_safe(stack_level)
and (socket.select({server}, {}, 0))[server]
)
if suspend
or step_into
or (step_over and stack_level <= step_level)
or has_breakpoint(file, line)
or (check_break and (socket.select({server}, {}, 0))[server]) then
or has_breakpoint(file, line) then
vars = vars or capture_vars()
check_break = true -- this is only needed to avoid breaking too early when debugging is starting
step_into = false
step_over = false
coroutine.resume(coro_debugger, events.BREAK, vars, file, line)
local status, res = coroutine.resume(coro_debugger, events.BREAK, vars, file, line)
if status and res then
abort = res
error(abort)
end -- abort execution if requested
end
if vars then restore_vars(vars) end
end
end
local function debugger_loop(sfile, sline)
local command
local app
local eval_env = {}
local function emptyWatch () return false end
local loaded = {}
for k in pairs(package.loaded) do loaded[k] = true end
while true do
local line, err
if server.settimeout then server:settimeout(0.010) end
if wx and server.settimeout then server:settimeout(0.1) end
while true do
line, err = server:receive()
if not line and err == "timeout" then
-- yield for wx GUI applications if possible to avoid "busyness"
if wx and wx.wxGetApp then
local app = wx.wxGetApp()
app = app or (wx and wx.wxGetApp and wx.wxGetApp())
if app then
local win = app:GetTopWindow()
if win then
-- process messages in a regular way
-- and exit as soon as the event loop is idle
win:Connect(wx.wxEVT_IDLE, function(event)
app:ExitMainLoop()
win:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wx.wxEVT_IDLE)
app:ExitMainLoop()
end)
app:MainLoop()
end
@@ -355,33 +412,37 @@ local function debugger_loop(sfile, sline)
size = tonumber(size)
if abort == nil then -- no LOAD/RELOAD allowed inside start()
if size > 0 then local _ = server:receive(size) end
if size > 0 then server:receive(size) end
if sfile and sline then
server:send("201 Started " .. sfile .. " " .. sline .. "\n")
else
server:send("200 OK 0\n")
end
else
if size == 0 then -- RELOAD the current script being debugged
server:send("200 OK 0\n")
abort = true
coroutine.yield() -- this should not return as the hook will abort
-- reset environment to allow required modules to load again
-- remove those packages that weren't loaded when debugger started
for k in pairs(package.loaded) do
if not loaded[k] then package.loaded[k] = nil end
end
local chunk = server:receive(size)
if chunk then -- LOAD a new script for debugging
local func, res = loadstring(chunk, name)
if func then
server:send("200 OK 0\n")
debugee = func
abort = true
coroutine.yield() -- this should not return as the hook will abort
else
server:send("401 Error in Expression " .. string.len(res) .. "\n")
server:send(res)
end
if size == 0 then -- RELOAD the current script being debugged
server:send("200 OK 0\n")
coroutine.yield("load")
else
server:send("400 Bad Request\n")
local chunk = server:receive(size)
if chunk then -- LOAD a new script for debugging
local func, res = loadstring(chunk, name)
if func then
server:send("200 OK 0\n")
debugee = func
coroutine.yield("load")
else
server:send("401 Error in Expression " .. string.len(res) .. "\n")
server:send(res)
end
else
server:send("400 Bad Request\n")
end
end
end
elseif command == "SETW" then
@@ -418,6 +479,8 @@ local function debugger_loop(sfile, sline)
server:send("202 Paused " .. file .. " " .. line .. "\n")
elseif ev == events.WATCH then
server:send("203 Paused " .. file .. " " .. line .. " " .. idx_watch .. "\n")
elseif ev == events.RESTART then
-- nothing to do
else
server:send("401 Error in Execution " .. string.len(file) .. "\n")
server:send(file)
@@ -432,6 +495,8 @@ local function debugger_loop(sfile, sline)
server:send("202 Paused " .. file .. " " .. line .. "\n")
elseif ev == events.WATCH then
server:send("203 Paused " .. file .. " " .. line .. " " .. idx_watch .. "\n")
elseif ev == events.RESTART then
-- nothing to do
else
server:send("401 Error in Execution " .. string.len(file) .. "\n")
server:send(file)
@@ -451,13 +516,17 @@ local function debugger_loop(sfile, sline)
server:send("202 Paused " .. file .. " " .. line .. "\n")
elseif ev == events.WATCH then
server:send("203 Paused " .. file .. " " .. line .. " " .. idx_watch .. "\n")
elseif ev == events.RESTART then
-- nothing to do
else
server:send("401 Error in Execution " .. string.len(file) .. "\n")
server:send(file)
end
elseif command == "SUSPEND" then
-- do nothing; it already fulfilled its role
elseif command == "EXIT" then
server:send("200 OK\n")
os.exit()
coroutine.yield("exit")
else
server:send("400 Bad Request\n")
end
@@ -468,7 +537,7 @@ local function connect(controller_host, controller_port)
return socket.connect(controller_host, controller_port)
end
-- Tries to start the debug session by connecting with a controller
-- Starts a debug session by connecting to a controller
local function start(controller_host, controller_port)
server = socket.connect(controller_host, controller_port)
if server then
@@ -485,32 +554,42 @@ local function start(controller_host, controller_port)
end
end
local function loop(controller_host, controller_port)
local function controller(controller_host, controller_port)
local exitonerror = not skip -- exit if not running a scratchpad
server = socket.connect(controller_host, controller_port)
if server then
local function report(trace, err)
local msg = err .. "\n" .. trace
server:send("401 Error in Execution " .. string.len(msg) .. "\n")
server:send(msg)
server:close()
return err
end
coro_debugger = coroutine.create(debugger_loop)
while true do
step_into = true
abort = false
coro_debugger = coroutine.create(debugger_loop)
if skip then skipcount = skip end -- to force suspend right away
local coro_debugee = coroutine.create(debugee)
debug.sethook(coro_debugee, debug_hook, "lcr")
local status, error = coroutine.resume(coro_debugee)
local status, err = coroutine.resume(coro_debugee)
-- was there an error or is the script done?
if not abort then -- this is an expected error; ignore it
if not status then -- this is something to be reported
return false,report(debug.traceback(coro_debugee), error)
-- 'abort' state is allowed here; ignore it
if abort then
if tostring(abort) == 'exit' then break end
else
if status then -- normal execution is done
break
elseif err and not err:find(deferror) then -- report the error
report(debug.traceback(coro_debugee), err)
if exitonerror then break end
-- resume once more to clear the response the debugger wants to send
local status, err = coroutine.resume(coro_debugger, events.RESTART)
if status and err == "exit" then break end
end
break
end
end
server:close()
@@ -521,6 +600,16 @@ local function loop(controller_host, controller_port)
return true
end
local function scratchpad(controller_host, controller_port, frequency)
skip = frequency or 100
return controller(controller_host, controller_port)
end
local function loop(controller_host, controller_port)
skip = nil -- just in case if loop() is called after scratchpad()
return controller(controller_host, controller_port)
end
local basedir = ""
-- Handles server debugging commands
@@ -530,7 +619,7 @@ local function handle(params, client)
if command == "run" or command == "step" or command == "out"
or command == "over" or command == "exit" then
client:send(string.upper(command) .. "\n")
client:receive()
client:receive() -- this should consume the first '200 OK' response
local breakpoint = client:receive()
if not breakpoint then
print("Program finished")
@@ -538,7 +627,9 @@ local function handle(params, client)
return -- use return here for those cases where os.exit() is not wanted
end
local _, _, status = string.find(breakpoint, "^(%d+)")
if status == "202" then
if status == "200" then
-- don't need to do anything
elseif status == "202" then
_, _, file, line = string.find(breakpoint, "^202 Paused%s+([%w%p%s]+)%s+(%d+)%s*$")
if file and line then
print("Paused at file " .. file .. " line " .. line)
@@ -559,7 +650,8 @@ local function handle(params, client)
else
print("Unknown error")
os.exit()
return nil, nil, "Unknown error" -- use return here for those cases where os.exit() is not wanted
-- use return here for those cases where os.exit() is not wanted
return nil, nil, "Debugger error: unexpected response '" .. breakpoint .. "'"
end
elseif command == "setb" then
_, _, _, file, line = string.find(params, "^([a-z]+)%s+([%w%p%s]+)%s+(%d+)%s*$")
@@ -633,23 +725,31 @@ local function handle(params, client)
for index, exp in pairs(watches) do
client:send("DELW " .. index .. "\n")
if client:receive() == "200 OK" then
watches[index] = nil
watches[index] = nil
else
print("Error: watch expression at index " .. index .. " [" .. exp .. "] not removed")
end
end
elseif command == "eval" or command == "exec"
or command == "load" or command == "reload" then
or command == "load" or command == "loadstring"
or command == "reload" then
local _, _, exp = string.find(params, "^[a-z]+%s+(.+)$")
if exp or (command == "reload") then
if command == "eval" then
exp = string.gsub(exp, "\n", " ") -- convert new lines
client:send("EXEC return " .. exp .. "\n")
elseif command == "exec" then
exp = string.gsub(exp, "\n", " ") -- convert new lines
if command == "eval" or command == "exec" then
exp = (exp:gsub("%-%-%[(=*)%[.-%]%1%]", "") -- remove comments
:gsub("%-%-.-\n", " ") -- remove line comments
:gsub("\n", " ")) -- convert new lines
if command == "eval" then exp = "return " .. exp end
client:send("EXEC " .. exp .. "\n")
elseif command == "reload" then
client:send("LOAD 0 -\n")
elseif command == "loadstring" then
local _, _, _, file, lines = string.find(exp, "^([\"'])(.-)%1%s+(.+)")
if not file then
_, _, file, lines = string.find(exp, "^(%S+)%s+(.+)")
end
client:send("LOAD " .. string.len(lines) .. " " .. file .. "\n")
client:send(lines)
else
local file = io.open(exp, "r")
if not file then print("Cannot open file " .. exp); return end
@@ -662,6 +762,9 @@ local function handle(params, client)
client:send(lines)
end
local params = client:receive()
if not params then
return nil, nil, "Debugger error: missing response after EXEC/LOAD"
end
local _, _, status, len = string.find(params, "^(%d+)[%w%p%s]+%s+(%d+)%s*$")
if status == "200" then
len = tonumber(len)
@@ -672,6 +775,9 @@ local function handle(params, client)
end
elseif status == "201" then
_, _, file, line = string.find(params, "^201 Started%s+([%w%p%s]+)%s+(%d+)%s*$")
elseif status == "202" or params == "200 OK" then
-- do nothing; this only happens when RE/LOAD command gets the response
-- that was for the original command that was aborted
elseif status == "401" then
len = tonumber(len)
local res = client:receive(len)
@@ -679,7 +785,7 @@ local function handle(params, client)
return nil, nil, res
else
print("Unknown error")
return nil, nil, "Unknown error"
return nil, nil, "Debugger error: unexpected response after EXEC/LOAD '" .. params .. "'"
end
else
print("Invalid command")
@@ -696,6 +802,8 @@ local function handle(params, client)
for i, v in pairs(watches) do
print("Watch exp. " .. i .. ": " .. v)
end
elseif command == "suspend" then
client:send("SUSPEND\n")
elseif command == "basedir" then
local _, _, dir = string.find(params, "^[a-z]+%s+(.+)$")
if dir then
@@ -772,6 +880,7 @@ end
-- make public functions available
mobdebug.listen = listen
mobdebug.loop = loop
mobdebug.scratchpad = scratchpad
mobdebug.handle = handle
mobdebug.connect = connect
mobdebug.start = start

93
lualibs/ssl.lua Normal file
View File

@@ -0,0 +1,93 @@
------------------------------------------------------------------------------
-- LuaSec 0.4.1
-- Copyright (C) 2006-2011 Bruno Silvestre
--
------------------------------------------------------------------------------
module("ssl", package.seeall)
require("ssl.core")
require("ssl.context")
_VERSION = "0.4.1"
_COPYRIGHT = "LuaSec 0.4.1 - Copyright (C) 2006-2011 Bruno Silvestre\n" ..
"LuaSocket 2.0.2 - Copyright (C) 2004-2007 Diego Nehab"
-- Export functions
rawconnection = core.rawconnection
rawcontext = context.rawcontext
--
--
--
local function optexec(func, param, ctx)
if param then
if type(param) == "table" then
return func(ctx, unpack(param))
else
return func(ctx, param)
end
end
return true
end
--
--
--
function newcontext(cfg)
local succ, msg, ctx
-- Create the context
ctx, msg = context.create(cfg.protocol)
if not ctx then return nil, msg end
-- Mode
succ, msg = context.setmode(ctx, cfg.mode)
if not succ then return nil, msg end
-- Load the key
if cfg.key then
succ, msg = context.loadkey(ctx, cfg.key, cfg.password)
if not succ then return nil, msg end
end
-- Load the certificate
if cfg.certificate then
succ, msg = context.loadcert(ctx, cfg.certificate)
if not succ then return nil, msg end
end
-- Load the CA certificates
if cfg.cafile or cfg.capath then
succ, msg = context.locations(ctx, cfg.cafile, cfg.capath)
if not succ then return nil, msg end
end
-- Set the verification options
succ, msg = optexec(context.setverify, cfg.verify, ctx)
if not succ then return nil, msg end
-- Set SSL options
succ, msg = optexec(context.setoptions, cfg.options, ctx)
if not succ then return nil, msg end
-- Set the depth for certificate verification
if cfg.depth then
succ, msg = context.setdepth(ctx, cfg.depth)
if not succ then return nil, msg end
end
return ctx
end
--
--
--
function wrap(sock, cfg)
local ctx, msg
if type(cfg) == "table" then
ctx, msg = newcontext(cfg)
if not ctx then return nil, msg end
else
ctx = cfg
end
local s, msg = core.create(ctx)
if s then
core.setfd(s, sock:getfd())
sock:setfd(core.invalidfd)
return s
end
return nil, msg
end

138
lualibs/ssl/https.lua Normal file
View File

@@ -0,0 +1,138 @@
----------------------------------------------------------------------------
-- LuaSec 0.4.1
-- Copyright (C) 2009-2011 PUC-Rio
--
-- Author: Pablo Musa
-- Author: Tomas Guisasola
---------------------------------------------------------------------------
local socket = require("socket")
local ssl = require("ssl")
local ltn12 = require("ltn12")
local http = require("socket.http")
local url = require("socket.url")
local table = require("table")
local string = require("string")
local try = socket.try
local type = type
local pairs = pairs
local getmetatable = getmetatable
module("ssl.https")
_VERSION = "0.4.1"
_COPYRIGHT = "LuaSec 0.4.1 - Copyright (C) 2009-2011 PUC-Rio"
-- Default settings
PORT = 443
local cfg = {
protocol = "tlsv1",
options = "all",
verify = "none",
}
--------------------------------------------------------------------
-- Auxiliar Functions
--------------------------------------------------------------------
-- Insert default HTTPS port.
local function default_https_port(u)
return url.build(url.parse(u, {port = PORT}))
end
-- Convert an URL to a table according to Luasocket needs.
local function urlstring_totable(url, body, result_table)
url = {
url = default_https_port(url),
method = body and "POST" or "GET",
sink = ltn12.sink.table(result_table)
}
if body then
url.source = ltn12.source.string(body)
url.headers = {
["content-length"] = #body,
["content-type"] = "application/x-www-form-urlencoded",
}
end
return url
end
-- Forward calls to the real connection object.
local function reg(conn)
local mt = getmetatable(conn.sock).__index
for name, method in pairs(mt) do
if type(method) == "function" then
conn[name] = function (self, ...)
return method(self.sock, ...)
end
end
end
end
-- Return a function which performs the SSL/TLS connection.
local function tcp(params)
params = params or {}
-- Default settings
for k, v in pairs(cfg) do
params[k] = params[k] or v
end
-- Force client mode
params.mode = "client"
-- 'create' function for LuaSocket
return function ()
local conn = {}
conn.sock = try(socket.tcp())
local st = getmetatable(conn.sock).__index.settimeout
function conn:settimeout(...)
return st(self.sock, ...)
end
-- Replace TCP's connection function
function conn:connect(host, port)
try(self.sock:connect(host, port))
self.sock = try(ssl.wrap(self.sock, params))
try(self.sock:dohandshake())
reg(self, getmetatable(self.sock))
return 1
end
return conn
end
end
--------------------------------------------------------------------
-- Main Function
--------------------------------------------------------------------
-- Make a HTTP request over secure connection. This function receives
-- the same parameters of LuaSocket's HTTP module (except 'proxy' and
-- 'redirect') plus LuaSec parameters.
--
-- @param url mandatory (string or table)
-- @param body optional (string)
-- @return (string if url == string or 1), code, headers, status
--
function request(url, body)
local result_table = {}
local stringrequest = type(url) == "string"
if stringrequest then
url = urlstring_totable(url, body, result_table)
else
url.url = default_https_port(url.url)
end
if http.PROXY or url.proxy then
return nil, "proxy not supported"
elseif url.redirect then
return nil, "redirect not supported"
elseif url.create then
return nil, "create function not permitted"
end
-- New 'create' function to establish a secure connection
url.create = tcp(url)
local res, code, headers, status = http.request(url)
if res and stringrequest then
return table.concat(result_table), code, headers, status
end
return res, code, headers, status
end

View File

@@ -54,12 +54,9 @@ function LoadFile(filePath, editor, file_must_exist)
return nil
end
if not editor then
editor = findDocumentToReuse()
end
if not editor then
editor = CreateEditor(wx.wxFileName(filePath):GetFullName() or "untitled.lua")
end
editor = editor
or findDocumentToReuse()
or CreateEditor(wx.wxFileName(filePath):GetFullName() or "untitled.lua")
editor:Clear()
editor:ClearAll()
@@ -261,6 +258,19 @@ function ClosePage(selection)
local id = editor:GetId()
if SaveModifiedDialog(editor, true) ~= wx.wxID_CANCEL then
DynamicWordsRemoveAll(editor)
local debugger = ide.debugger
-- check if the window with the scratchpad running is being closed
if debugger and debugger.scratchpad and debugger.scratchpad.editor == editor then
DebuggerScratchpadOff()
end
-- check if the debugger is running and is using the current window
-- abort the debugger if the current marker is in the window being closed
-- also abort the debugger if it is running, as we don't know what
-- window will need to be activated when the debugger is paused
if debugger and debugger.pid and
(debugger.running or editor:MarkerNext(0, CURRENT_LINE_MARKER_VALUE) >= 0) then
debugger.terminate()
end
removePage(ide.openDocuments[id].index)
end
end
@@ -383,20 +393,20 @@ function ClearAllCurrentLineMarkers()
end
end
function CompileProgram(editor)
function CompileProgram(editor, quiet)
local editorText = editor:GetText()
local id = editor:GetId()
local filePath = DebuggerMakeFileName(editor, openDocuments[id].filePath)
local ret, errMsg, line_num = wxlua.CompileLuaScript(editorText, filePath)
if ide.frame.menuBar:IsChecked(ID_CLEAROUTPUT) then
ClearOutput()
end
local _, errMsg, line_num = wxlua.CompileLuaScript(editorText, filePath)
if ide.frame.menuBar:IsChecked(ID_CLEAROUTPUT) then ClearOutput() end
if line_num > -1 then
DisplayOutput("Compilation error on line number :"..tostring(line_num).."\n"..errMsg.."\n")
editor:GotoLine(line_num-1)
DisplayOutput("Compilation error on line "..tostring(line_num)..":\n"..
errMsg:gsub("Lua:.-\n", "").."\n")
if not quiet then editor:GotoLine(line_num-1) end
else
DisplayOutput("Compilation successful.\n")
if not quiet then DisplayOutput("Compilation successful.\n") end
end
return line_num == -1 -- return true if it compiled ok
@@ -474,6 +484,7 @@ function ShowFullScreen(setFullScreen)
beforeFullScreenPerspective = uimgr:SavePerspective()
uimgr:GetPane("bottomnotebook"):Show(false)
uimgr:GetPane("projpanel"):Show(false)
SetEditorSelection() -- make sure the focus is on the editor
elseif beforeFullScreenPerspective then
uimgr:LoadPerspective(beforeFullScreenPerspective)
beforeFullScreenPerspective = nil
@@ -485,11 +496,11 @@ function ShowFullScreen(setFullScreen)
end
function CloseWindow(event)
exitingProgram = true -- don't handle focus events
ide.exitingProgram = true -- don't handle focus events
if not SaveOnExit(event:CanVeto()) then
event:Veto()
exitingProgram = false
ide.exitingProgram = false
return
end

View File

@@ -36,7 +36,7 @@ local function updateWatches()
end
local function activateDocument(fileName, line)
if (not fileName and line) then return end
if not fileName then return end
if not wx.wxIsAbsolutePath(fileName) then
fileName = wx.wxGetCwd().."/"..fileName
@@ -46,8 +46,8 @@ local function activateDocument(fileName, line)
fileName = wx.wxUnix2DosFilename(fileName)
end
local fileFound = false
for id, document in pairs(ide.openDocuments) do
local activated
for _, document in pairs(ide.openDocuments) do
local editor = document.editor
-- for running in cygwin, use same type of separators
filePath = string.gsub(document.filePath, "\\", "/")
@@ -57,14 +57,16 @@ local function activateDocument(fileName, line)
notebook:SetSelection(selection)
SetEditorSelection(selection)
ClearAllCurrentLineMarkers()
editor:MarkerAdd(line-1, CURRENT_LINE_MARKER)
editor:EnsureVisibleEnforcePolicy(line-1)
fileFound = true
if line then
editor:MarkerAdd(line-1, CURRENT_LINE_MARKER)
editor:EnsureVisibleEnforcePolicy(line-1)
end
activated = editor
break
end
end
return fileFound
return activated ~= nil, activated
end
debugger.shell = function(expression)
@@ -73,6 +75,7 @@ debugger.shell = function(expression)
local addedret = false
local value, _, err = debugger.handle('exec ' .. expression)
if err and (err:find("'=' expected near '<eof>'") or
err:find("syntax error near '") or
err:find("unexpected symbol near '")) then
value, _, err = debugger.handle('eval ' .. expression:gsub("^%s*=%s*",""))
addedret = true
@@ -93,8 +96,8 @@ debugger.listen = function()
DisplayOutput("Started debugger server; clients can connect to "..wx.wxGetHostName()..":"..debugger.portnumber..".\n")
copas.autoclose = false
copas.addserver(server, function (skt)
SetAllEditorsReadOnly(true)
local options = debugger.options or {}
if not debugger.scratchpad then SetAllEditorsReadOnly(true) end
local wxfilepath = GetEditorFileAndCurInfo()
local startfile = options.startfile or wxfilepath:GetFullPath()
local basedir = options.basedir
@@ -105,6 +108,7 @@ debugger.listen = function()
debugger.server = copas.wrap(skt)
debugger.socket = skt
debugger.loop = false
debugger.scratchable = false
-- load the remote file into the debugger
-- set basedir first, before loading to make sure that the path is correct
@@ -116,21 +120,25 @@ debugger.listen = function()
debugger.handle("delallb")
-- go over all windows and find all breakpoints
for id, document in pairs(ide.openDocuments) do
local editor = document.editor
local filePath = document.filePath
line = editor:MarkerNext(0, BREAKPOINT_MARKER_VALUE)
while line ~= -1 do
debugger.handle("setb " .. filePath .. " " .. (line+1))
line = editor:MarkerNext(line + 1, BREAKPOINT_MARKER_VALUE)
if (not debugger.scratchpad) then
for _, document in pairs(ide.openDocuments) do
local editor = document.editor
local filePath = document.filePath
line = editor:MarkerNext(0, BREAKPOINT_MARKER_VALUE)
while line ~= -1 do
debugger.handle("setb " .. filePath .. " " .. (line+1))
line = editor:MarkerNext(line + 1, BREAKPOINT_MARKER_VALUE)
end
end
end
if (options.run) then
local file, line = debugger.handle("run")
activateDocument(file, line)
elseif (debugger.scratchpad) then
debugger.scratchpad.updated = true
else
local file, line = debugger.handle("load " .. startfile)
local file, line, err = debugger.loadfile(startfile)
-- "load" can work in two ways: (1) it can load the requested file
-- OR (2) it can "refuse" to load it if the client was started
-- with start() method, which can't load new files
@@ -162,12 +170,16 @@ debugger.listen = function()
DisplayOutput("Can't find file '" .. file .. "' to activate for debugging; open the file before debugging.\n")
return debugger.terminate()
end
elseif err then
DisplayOutput("Can't debug the script in the active editor window. Compilation error:\n" .. err .. "\n")
return debugger.terminate()
else
debugger.scratchable = true
activateDocument(startfile, 1)
end
end
if (not options.noshell) then
if (not options.noshell and not debugger.scratchpad) then
ShellSupportRemote(debugger.shell, debugger.pid)
end
@@ -197,7 +209,6 @@ end
debugger.exec = function(command)
if debugger.server and not debugger.running then
copas.addthread(function ()
local out
while true do
@@ -227,22 +238,25 @@ debugger.exec = function(command)
end
end
debugger.updateBreakpoint = function(command)
debugger.handleAsync = function(command)
if debugger.server and not debugger.running then
copas.addthread(function ()
debugger.handle(command)
end)
copas.addthread(function () debugger.handle(command) end)
end
end
debugger.loadfile = function(file)
return debugger.handle("load " .. file)
end
debugger.loadstring = function(file, string)
return debugger.handle("loadstring '" .. file .. "' " .. string)
end
debugger.update = function() copas.step(0) end
debugger.terminate = function()
if debugger.server then
if debugger.pid then -- if there is PID, try local kill
DebuggerKillClient()
else -- otherwise, trace graceful exit for the remote process
debugger.exec("exit")
copas.step(1) -- process 'exit' right away; doesn't guarantee the response
else -- otherwise, try graceful exit for the remote process
debugger.breaknow("exit")
end
DebuggerStop()
end
@@ -256,28 +270,30 @@ debugger.over = function() debugger.exec("over") end
debugger.out = function() debugger.exec("out") end
debugger.run = function() debugger.exec("run") end
debugger.evaluate = function(expression) return debugger.handle('eval ' .. expression) end
debugger.breaknow = function()
debugger.execute = function(expression) return debugger.handle('exec ' .. expression) end
debugger.breaknow = function(command)
-- stop if we're running a "trace" command
debugger.loop = false
-- force a step command; don't use copas interface as it checks
-- force suspend command; don't use copas interface as it checks
-- for the other side "reading" and the other side is not reading anything.
-- use the "original" socket to write a "step" command.
-- use the "original" socket to send "suspend" command.
-- this will only break on the next Lua command.
if debugger.socket then
local running = debugger.running
-- this needs to be short as it will block the UI
debugger.socket:settimeout(0.25)
local file, line, err = debugger.handle("step", debugger.socket)
local file, line, err = debugger.handle(command or "suspend", debugger.socket)
debugger.socket:settimeout(0)
-- restore running status
debugger.running = running
-- don't need to do anything else as the earlier call (run, step, etc.)
-- will get the results (file, line) back and will update the UI
return file, line, err
end
end
debugger.breakpoint = function(file, line, state)
debugger.updateBreakpoint((state and "setb " or "delb ") .. file .. " " .. line)
debugger.handleAsync((state and "setb " or "delb ") .. file .. " " .. line)
end
----------------------------------------------
@@ -311,6 +327,7 @@ function DebuggerStop()
SetAllEditorsReadOnly(false)
ShellSupportRemote(nil, 0)
ClearAllCurrentLineMarkers()
DebuggerScratchpadOff()
DisplayOutput("Completed debugging session.\n")
end
end
@@ -391,7 +408,7 @@ function DebuggerCreateWatchWindow()
end)
watchWindow:Connect(ID_ADDWATCH, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
function ()
local row = watchListCtrl:InsertItem(watchListCtrl:GetItemCount(), "Expr")
watchListCtrl:SetItem(row, 0, "Expr")
watchListCtrl:SetItem(row, 1, "Value")
@@ -399,7 +416,7 @@ function DebuggerCreateWatchWindow()
end)
watchWindow:Connect(ID_EDITWATCH, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
function ()
local row = findSelectedWatchItem()
if row >= 0 then
watchListCtrl:EditLabel(row)
@@ -411,7 +428,7 @@ function DebuggerCreateWatchWindow()
end)
watchWindow:Connect(ID_REMOVEWATCH, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
function ()
local row = findSelectedWatchItem()
if row >= 0 then
watchListCtrl:DeleteItem(row)
@@ -423,9 +440,7 @@ function DebuggerCreateWatchWindow()
end)
watchWindow:Connect(ID_EVALUATEWATCH, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
updateWatches()
end)
function () updateWatches() end)
watchWindow:Connect(ID_EVALUATEWATCH, wx.wxEVT_UPDATE_UI,
function (event)
event:Enable(watchListCtrl:GetItemCount() > 0)
@@ -465,3 +480,202 @@ function DebuggerToggleBreakpoint(editor, line)
end
end
end
-- scratchpad functions
function DebuggerRefreshScratchpad()
if debugger.scratchpad and debugger.scratchpad.updated then
if debugger.scratchpad.running then
-- break the current execution first
-- don't try too frequently to avoid overwhelming the debugger
local now = os.clock()
if now - debugger.scratchpad.running > 0.250 then
debugger.breaknow()
debugger.scratchpad.running = now
end
else
local clear = ide.frame.menuBar:IsChecked(ID_CLEAROUTPUT)
local scratchpadEditor = debugger.scratchpad.editor
local code = scratchpadEditor:GetText()
local filePath = DebuggerMakeFileName(scratchpadEditor,
ide.openDocuments[scratchpadEditor:GetId()].filePath)
-- this is a special error message that is generated at the very end
-- of each script to avoid exiting the (debugee) scratchpad process.
-- these errors are handled and not reported to the user
local errormsg = 'execution suspended at ' .. os.clock()
local stopper = "\ndo error('" .. errormsg .. "') end"
local function reloadScratchpadCode()
debugger.scratchpad.running = os.clock()
debugger.scratchpad.updated = false
local _, _, err = debugger.loadstring(filePath, code .. stopper)
local prefix = "Compilation error"
if clear then ClearOutput() end
if not err then
_, _, err = debugger.handle("run")
prefix = "Execution error"
end
if err and not err:find(errormsg) then
local line = err:match('.*%[string "[%w:/%\\_%-%.]+"%]:(%d+)%s*:')
-- check if the line number in the error matches the line we added
-- to stop the script; if so, compile it the usual way and report.
-- the usual way is not used all the time because it is slow
if prefix == "Compilation error" and
tonumber(line) == scratchpadEditor:GetLineCount()+1 then
_, err, line = wxlua.CompileLuaScript(code, filePath)
err = err:gsub("Lua:.-\n", "") -- remove "syntax error" part
end
DisplayOutput(prefix .. (line and " on line " .. line or "") .. ":\n"
.. err:gsub('stack traceback:.+', ''):gsub('\n+$', '') .. "\n")
end
debugger.scratchpad.running = false
end
copas.addthread(reloadScratchpadCode)
end
end
end
local numberStyle = wxstc.wxSTC_LUA_NUMBER
function DebuggerScratchpadOn(editor)
debugger.scratchpad = {editor = editor}
-- check if the debugger is already running; this happens when
-- scratchpad is turned on after external script has connected
if debugger.server then
debugger.scratchpad.updated = true
ClearAllCurrentLineMarkers()
SetAllEditorsReadOnly(false)
ShellSupportRemote(nil, 0) -- disable remote shell
DebuggerRefreshScratchpad()
elseif not ProjectDebug(true, "scratchpad") then
debugger.scratchpad = nil
return
end
local scratchpadEditor = editor
scratchpadEditor:StyleSetUnderline(numberStyle, true)
scratchpadEditor:Connect(wxstc.wxEVT_STC_MODIFIED, function(event)
local evtype = event:GetModificationType()
if (bit.band(evtype,wxstc.wxSTC_MOD_INSERTTEXT) ~= 0 or
bit.band(evtype,wxstc.wxSTC_MOD_DELETETEXT) ~= 0 or
bit.band(evtype,wxstc.wxSTC_PERFORMED_UNDO) ~= 0 or
bit.band(evtype,wxstc.wxSTC_PERFORMED_REDO) ~= 0) then
debugger.scratchpad.updated = true
end
event:Skip()
end)
scratchpadEditor:Connect(wx.wxEVT_LEFT_DOWN, function(event)
local scratchpad = debugger.scratchpad
local point = event:GetPosition()
local pos = scratchpadEditor:PositionFromPoint(point)
-- are we over a number in the scratchpad? if not, it's not our event
if ((not scratchpad) or
(bit.band(scratchpadEditor:GetStyleAt(pos),31) ~= numberStyle)) then
event:Skip()
return
end
-- find start position and length of the number
local text = scratchpadEditor:GetText()
local nstart = pos
while nstart >= 0
and (bit.band(scratchpadEditor:GetStyleAt(nstart),31) == numberStyle)
do nstart = nstart - 1 end
local nend = pos
while nend < string.len(text)
and (bit.band(scratchpadEditor:GetStyleAt(nend),31) == numberStyle)
do nend = nend + 1 end
-- check if there is minus sign right before the number and include it
if nstart >= 0 and scratchpadEditor:GetTextRange(nstart,nstart+1) == '-' then
nstart = nstart - 1
end
scratchpad.start = nstart + 1
scratchpad.length = nend - nstart - 1
scratchpad.origin = tonumber(scratchpadEditor:GetTextRange(nstart+1,nend))
if scratchpad.origin then
scratchpad.point = point
scratchpadEditor:CaptureMouse()
end
end)
scratchpadEditor:Connect(wx.wxEVT_LEFT_UP, function(event)
if debugger.scratchpad and debugger.scratchpad.point then
debugger.scratchpad.point = nil
debugger.scratchpad.editor:ReleaseMouse()
wx.wxSetCursor(wx.wxNullCursor) -- restore cursor
else event:Skip() end
end)
scratchpadEditor:Connect(wx.wxEVT_MOTION, function(event)
local point = event:GetPosition()
local pos = scratchpadEditor:PositionFromPoint(point)
local scratchpad = debugger.scratchpad
local ipoint = scratchpad and scratchpad.point
-- record the fact that we are over a number or dragging slider
scratchpad.over = scratchpad and
(ipoint or (bit.band(scratchpadEditor:GetStyleAt(pos),31) == numberStyle))
if ipoint then
-- calculate difference in point position
local dx = point.x - ipoint.x
local dy = - (point.y - ipoint.y) -- invert dy as y increases down
-- re-calculate the value
local startpos = scratchpad.start
local endpos = scratchpad.start+scratchpad.length
local num = tonumber(scratchpad.origin) + dx/10
-- update length
scratchpad.length = string.len(num)
-- update the value in the document
scratchpadEditor:SetTargetStart(startpos)
scratchpadEditor:SetTargetEnd(endpos)
scratchpadEditor:ReplaceTarget("" .. num)
else event:Skip() end
end)
scratchpadEditor:Connect(wx.wxEVT_SET_CURSOR, function(event)
if (debugger.scratchpad and debugger.scratchpad.over) then
event:SetCursor(wx.wxCursor(wx.wxCURSOR_SIZEWE))
else event:Skip() end
end)
return true
end
function DebuggerScratchpadOff()
if not debugger.scratchpad then return end
local scratchpadEditor = debugger.scratchpad.editor
scratchpadEditor:StyleSetUnderline(numberStyle, false)
scratchpadEditor:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wxstc.wxEVT_STC_MODIFIED)
scratchpadEditor:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wx.wxEVT_MOTION)
scratchpadEditor:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wx.wxEVT_LEFT_DOWN)
scratchpadEditor:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wx.wxEVT_LEFT_UP)
scratchpadEditor:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wx.wxEVT_SET_CURSOR)
debugger.scratchpad = nil
debugger.terminate()
-- disable menu if it is still enabled
-- (as this may be called when the debugger is being shut down)
local menuBar = ide.frame.menuBar
if menuBar:IsChecked(ID_RUNNOW) then menuBar:Check(ID_RUNNOW, false) end
return true
end

View File

@@ -3,11 +3,9 @@
---------------------------------------------------------
local wxkeywords = nil -- a string of the keywords for scintilla of wxLua's wx.XXX items
local in_evt_focus = false -- true when in editor focus event to avoid recursion
local editorID = 100 -- window id to create editor pages with, incremented for new editors
local openDocuments = ide.openDocuments
local ignoredFilesList = ide.ignoredFilesList
local statusBar = ide.frame.statusBar
local notebook = ide.frame.notebook
local funclist = ide.frame.toolBar.funclist
@@ -17,7 +15,7 @@ local projcombobox = ide.frame.projpanel.projcombobox
-- ----------------------------------------------------------------------------
-- Update the statusbar text of the frame using the given editor.
-- Only update if the text has changed.
statusTextTable = { "OVR?", "R/O?", "Cursor Pos" }
local statusTextTable = { "OVR?", "R/O?", "Cursor Pos" }
-- set funclist font to be the same as the combobox in the project dropdown
funclist:SetFont(projcombobox:GetFont())
@@ -62,6 +60,11 @@ local function updateBraceMatch(editor)
pos = (match[char] and pos) or (charp and match[charp] and posp)
if (pos) then
-- don't match brackets in markup comments
local style = bit.band(editor:GetStyleAt(pos), 31)
if MarkupIsSpecial and MarkupIsSpecial(style)
or editor.spec.iscomment[style] then return end
local pos2 = editor:BraceMatch(pos)
if (pos2 == wxstc.wxSTC_INVALID_POSITION) then
editor:BraceBadLight(pos)
@@ -157,7 +160,7 @@ function GetEditorFileAndCurInfo(nochecksave)
return
end
local id = editor:GetId();
local id = editor:GetId()
local filepath = openDocuments[id].filePath
if (nochecksave and not filepath) then
return
@@ -303,7 +306,6 @@ function CreateEditor(name)
DynamicWordsRem("pre",editor,nil,editor:LineFromPosition(event:GetPosition()), numlines)
end
if (bit.band(evtype,wxstc.wxSTC_MOD_BEFOREINSERT) ~= 0) then
local pos = event:GetPosition()
DynamicWordsRem("pre",editor,nil,editor:LineFromPosition(event:GetPosition()), 0)
end
end)
@@ -319,7 +321,7 @@ function CreateEditor(name)
local linestart = editor:PositionFromLine(line)
local localpos = pos-linestart
linetxtopos = linetx:sub(1,localpos)
local linetxtopos = linetx:sub(1,localpos)
if (ch == char_CR and eol==2) or (ch == char_LF and eol==0) then
if (line > 0) then
@@ -374,11 +376,12 @@ function CreateEditor(name)
function (event)
updateStatusText(editor)
updateBraceMatch(editor)
for e,iv in ipairs(editor.ev) do
for _,iv in ipairs(editor.ev) do
local line = editor:LineFromPosition(iv[1])
IndicateFunctions(editor,line,line+iv[2])
if MarkupStyle then MarkupStyle(editor,line,line+iv[2]) end
if MarkupStyle then MarkupStyle(editor,line,line+iv[2]+1) end
end
if MarkupStyleRefresh then MarkupStyleRefresh(editor, editor.ev) end
editor.ev = {}
end)
@@ -396,12 +399,33 @@ function CreateEditor(name)
editor:Connect(wx.wxEVT_SET_FOCUS,
function (event)
event:Skip()
if ide.in_evt_focus or exitingProgram then return end
ide.in_evt_focus = true
if ide.in_evt_focus or ide.exitingProgram then return end
ide.in_evt_focus = true -- true when in editor focus event to avoid recursion
isFileAlteredOnDisk(editor)
ide.in_evt_focus = false
end)
editor:Connect(wx.wxEVT_KEY_DOWN,
function (event)
local keycode = event:GetKeyCode()
local first, last = 0, notebook:GetPageCount()-1
if keycode == wx.WXK_ESCAPE and frame:IsFullScreen() then
ShowFullScreen(false)
elseif event:ControlDown() and
(keycode == wx.WXK_PAGEUP or keycode == wx.WXK_TAB and event:ShiftDown()) then
if notebook:GetSelection() == first
then notebook:SetSelection(last)
else notebook:AdvanceSelection(false) end
elseif event:ControlDown() and
(keycode == wx.WXK_PAGEDOWN or keycode == wx.WXK_TAB) then
if notebook:GetSelection() == last
then notebook:SetSelection(first)
else notebook:AdvanceSelection(true) end
else
event:Skip()
end
end)
if notebook:AddPage(editor, name, true) then
local id = editor:GetId()
local document = {}
@@ -424,7 +448,7 @@ function GetSpec(ext,forcespec)
-- allow forcespec for "override"
if ext and not spec then
for i,curspec in pairs(ide.specs) do
exts = curspec.exts
local exts = curspec.exts
if (exts) then
for n,curext in ipairs(exts) do
if (curext == ext) then

View File

@@ -23,12 +23,8 @@ ide.filetree = {
root_id = nil,
rootdir = "",
},
imglist,
newfiledir,
}
local filetree = ide.filetree
local frame = ide.frame
-- generic tree
-- ------------
@@ -58,7 +54,7 @@ local function treeAddDir(tree,parent_id,rootdir)
local dirs = FileSysGet(search,wx.wxDIR)
-- append directories
for i,dir in ipairs(dirs) do
for _,dir in ipairs(dirs) do
local name = dir:match("%"..string_Pathsep.."("..stringset_File.."+)$")
local icon = 0
local item = items[name .. icon]
@@ -83,9 +79,9 @@ local function treeAddDir(tree,parent_id,rootdir)
-- then append files
local files = FileSysGet(search,wx.wxFILE)
for i,file in ipairs(files) do
for _,file in ipairs(files) do
local name = file:match("%"..string_Pathsep.."("..stringset_File.."+)$")
local known = GetSpec(GetFileExt(fname))
local known = GetSpec(GetFileExt(name))
local icon = known and 1 or 2
local item = items[name .. icon]
if item then -- existing item
@@ -160,10 +156,7 @@ local function treeSetConnectorsAndIcons(tree,treedata)
return true
end )
tree:Connect( wx.wxEVT_COMMAND_TREE_ITEM_COLLAPSED,
function( event )
-- don't need to do anything here
return true
end )
function() return true end )
tree:Connect( wx.wxEVT_COMMAND_TREE_ITEM_ACTIVATED,
function( event )
local item_id = event:GetItem()
@@ -185,21 +178,6 @@ local function treeSetConnectorsAndIcons(tree,treedata)
end
end
end )
tree:Connect( wx.wxEVT_COMMAND_TREE_SEL_CHANGED,
function( event )
local item_id = event:GetItem()
-- set "newfile-path"
local isfile = tree:GetItemImage(item_id) ~= 0
filetree.newfiledir = treeGetItemFullName(tree,treedata,item_id)
if (isfile) then
-- remove file
filetree.newfiledir = wx.wxFileName(filetree.newfiledir):GetPath(wx.wxPATH_GET_VOLUME)
end
filetree.newfiledir = filetree.newfiledir..string_Pathsep
end )
end
-- project

View File

@@ -113,26 +113,27 @@ local function createNotebook(frame)
wxaui.wxAUI_NB_DEFAULT_STYLE + wxaui.wxAUI_NB_TAB_EXTERNAL_MOVE
+ wx.wxNO_BORDER)
-- the following group of event handlers allows the active editor
-- to get/keep focus after execution of Run and other commands
local current -- the currently active editor, needed by the focus selection
local function onPageChange(event)
current = event:GetSelection() -- update the active editor reference
SetEditorSelection(current)
event:Skip() -- skip to let page change
end
notebook:Connect(wx.wxEVT_SET_FOCUS, -- Notepad tabs shouldn't be selectable,
function (event)
SetEditorSelection(current) -- select the currently active editor
end)
notebook:Connect(wx.wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED, onPageChange)
notebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED, onPageChange)
notebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE,
function (event)
ClosePage(event:GetSelection())
event:Veto() -- don't propagate the event as the page is already closed
end)
notebook:Connect(wx.wxEVT_SET_FOCUS, -- Notepad tabs shouldn't be selectable,
function (event)
SetEditorSelection(current) -- select the currently active editor
end)
frame.notebook = notebook
return notebook
end

View File

@@ -41,6 +41,7 @@ ID_SORT = NewID()
ID_TOGGLEBREAKPOINT = NewID()
ID_COMPILE = NewID()
ID_RUN = NewID()
ID_RUNNOW = NewID()
ID_ATTACH_DEBUG = NewID()
ID_START_DEBUG = NewID()
--ID_USECONSOLE = NewID()

150
src/editor/inspect.lua Normal file
View File

@@ -0,0 +1,150 @@
-- Integration with LuaInspect
-- (C) 2012 Paul Kulchenko
require "metalua"
local M = {}
local LA = require "luainspect.ast"
local LI = require "luainspect.init"
local T = require "luainspect.types"
local FAST = true
if FAST then
LI.eval_comments = function () end
LI.infer_values = function () end
end
function M.warnings_from_string(src, file)
local ast, err, linenum, colnum = LA.ast_from_string(src, file)
if err then return nil, err, linenum, colnum end
if FAST then
LI.inspect(ast, nil, src)
LA.ensure_parents_marked(ast)
else
local tokenlist = LA.ast_to_tokenlist(ast, src)
LI.inspect(ast, tokenlist, src)
LI.mark_related_keywords(ast, tokenlist, src)
end
return M.show_warnings(ast)
end
function M.show_warnings(top_ast)
local warnings = {}
local function warn(msg, linenum, path)
warnings[#warnings+1] = (path or "?") .. "(" .. (linenum or 0) .. "): " .. msg
end
local function known(o) return not T.istype[o] end
local isseen, globseen = {}, {}
LA.walk(top_ast, function(ast)
local line = ast.lineinfo and ast.lineinfo.first[1] or 0
local path = ast.lineinfo and ast.lineinfo.first[4] or '?'
-- check if we're masking a variable in the same scope
if ast.localmasking and
ast.level == ast.localmasking.level then
local linenum = ast.localmasking.lineinfo.first[1]
warn("local variable '" .. ast[1] .. "' masks earlier declaration " ..
(linenum and "on line " .. linenum or "in the same scope"),
line, path)
end
if ast.localdefinition == ast and not ast.isused and
not ast.isignore then
local parent = ast.parent and ast.parent.parent
local isparam = parent and parent.tag == 'Function'
if isparam then
if ast[1] ~= 'self' then
local func = parent.parent and parent.parent.parent
local name = type(func[1][1][1]) == 'string' and func[1][1][1]
-- "function foo(bar)" => func.tag == 'Set'
-- "local function foo(bar)" => func.tag == 'Localrec'
-- "local _, foo = 1, function(bar)" => func.tag == 'Local'
-- "print(function(bar) end)" => func.tag == nil
warn("unused parameter '" .. ast[1] .. "'" ..
(func and (not func.tag or func.tag == 'Set' or func.tag == 'Localrec')
and (name and func.tag
and (" in function '" .. name .. "'")
or " in anonymous function")
or ""),
line, path)
end
else
warn("unused local variable '" .. ast[1] ..
"'; consider removing or replacing with '_'",
line, path)
end
end
-- added check for FAST as ast.seevalue relies on value evaluation,
-- which is very slow even on simple and short scripts
if not FAST and ast.isfield and not(known(ast.seevalue.value) and ast.seevalue.value ~= nil) then
warn("unknown field " .. ast[1], ast.lineinfo.first[1], path)
elseif ast.tag == 'Id' and not ast.localdefinition and not ast.definedglobal then
warn("unknown global variable '" .. ast[1] .. "'", line, path)
elseif ast.tag == 'Id' and not ast.localdefinition and ast.definedglobal then
local parent = ast.parent and ast.parent.parent
if parent and parent.tag == 'Set' and not globseen[ast[1]] -- report assignments to global
and parent[2][1].tag ~= "Function" then -- but ignore global functions
warn("first assignment to global variable '" .. ast[1] .. "'", line, path)
globseen[ast[1]] = true
end
end
local vast = ast.seevalue or ast
local note = vast.parent
and (vast.parent.tag == 'Call' or vast.parent.tag == 'Invoke')
and vast.parent.note
if note and not isseen[vast.parent] then
isseen[vast.parent] = true
warn("function '" .. ast[1] .. "': " .. note, line, path)
end
end)
return warnings
end
local frame = ide.frame
local menu = frame.menuBar:GetMenu(frame.menuBar:FindMenu("&Project"))
local ID_ANALYZE = ID "debug.analyze"
-- insert after "Compile" item
for item = 0, menu:GetMenuItemCount()-1 do
if menu:FindItemByPosition(item):GetId() == ID_COMPILE then
menu:Insert(item+1, ID_ANALYZE, "Analyze\tShift-F7", "Analyze the source code")
break
end
end
local debugger = ide.debugger
local openDocuments = ide.openDocuments
local function analyzeProgram(editor)
local editorText = editor:GetText()
local id = editor:GetId()
local filePath = DebuggerMakeFileName(editor, openDocuments[id].filePath)
if frame.menuBar:IsChecked(ID_CLEAROUTPUT) then ClearOutput() end
DisplayOutput("Analizing the source code")
frame:Update()
local warn, err = M.warnings_from_string(editorText, filePath)
if err then -- report compilation error
DisplayOutput(": not completed\n")
return false
end
DisplayOutput(": " .. (warn and #warn > 0 and (#warn .. " warnings") or "no warnings.") .. "\n")
DisplayOutputNoMarker(table.concat(warn, "\n") .. "\n")
return true -- analyzed ok
end
frame:Connect(ID_ANALYZE, wx.wxEVT_COMMAND_MENU_SELECTED,
function ()
ActivateOutput()
local editor = GetEditor()
if not analyzeProgram(editor) then CompileProgram(editor) end
end)
frame:Connect(ID_ANALYZE, wx.wxEVT_UPDATE_UI,
function (event)
local editor = GetEditor()
event:Enable((debugger.server == nil and debugger.pid == nil) and (editor ~= nil))
end)

View File

@@ -1,14 +1,16 @@
-- Copyright (C) Paul Kulchenko 2011-2012
-- update styles for comment markup
-- styles for comment markup
local styles = ide.config.styles
local comment = styles.comment
local MD_MARK_ITAL = '_' -- italic
local MD_MARK_BOLD = '*' -- bold
local MD_MARK_LINK = '~' -- link
local MD_MARK_HEAD = '!' -- header
local MD_MARK_CODE = '@' -- code
local MD_MARK_BOLD = '**' -- bold
local MD_MARK_LINK = '[' -- link
local MD_MARK_LINT = ')' -- link terminator
local MD_MARK_HEAD = '#' -- header
local MD_MARK_CODE = '`' -- code
local MD_MARK_BOXD = '|' -- highlight
local MD_MARK_LSEP = ';' -- link separator (between text and link)
local MD_MARK_LSEP = '](' -- link separator (between text and link)
local MD_MARK_MARK = ' ' -- separator
local MD_LINK_NEWWINDOW = '+' -- indicator to open a new window for links
local markup = {
@@ -21,10 +23,15 @@ local markup = {
[MD_MARK_MARK] = {st=31, fg=comment.fg, bg=comment.bg, v=false},
}
-- allow other editor features to recognize this special markup
function MarkupIsSpecial(style) return style == 31 end
local function q(s) return s:gsub('(.)','%%%1') end
local MD_MARK_PTRN = '' -- combination of all markup marks that can start styling
for key,value in pairs(markup) do
styles[key] = value
if key ~= MD_MARK_MARK then MD_MARK_PTRN = MD_MARK_PTRN .. "%" .. key end
if key ~= MD_MARK_MARK then MD_MARK_PTRN = MD_MARK_PTRN .. q(key) end
end
function MarkupHotspotClick(pos, editor)
@@ -35,14 +42,11 @@ function MarkupHotspotClick(pos, editor)
end
local line = editor:LineFromPosition(pos)
local tx = editor:GetLine(line)
pos = pos + 1 - editor:PositionFromLine(line) -- turn into relative position
pos = pos + #MD_MARK_LINK - editor:PositionFromLine(line) -- turn into relative position
-- find the separator on the right side of the position
local poss = string.find(tx, MD_MARK_LSEP, pos, true)
local pose = string.find(tx, MD_MARK_LINK, pos, true)
if (poss and pose) then
local text = string.sub(tx, poss+1, pose-1)
-- extract the URL/command on the right side of the separator
local _,_,text = string.find(tx, q(MD_MARK_LSEP).."([^%s]+)"..q(MD_MARK_LINT), pos)
if text then
local filepath = ide.openDocuments[editor:GetId()].filePath
local _,_,shell = string.find(text, [[^macro:shell%((.*%S)%)$]])
local _,_,http = string.find(text, [[^(http:%S+)$]])
@@ -78,22 +82,35 @@ local function ismarkup (tx)
local start = 1
while true do
-- find a separator first
local st,_,sep = string.find(tx, "(["..MD_MARK_PTRN.."])", start)
local st,_,sep,more = string.find(tx, "(["..MD_MARK_PTRN.."])(.)", start)
if not st then return end
-- check if this is a first character of a multi-character separator
if not markup[sep] then sep = sep .. (more or '') end
local s,e,cap
local qsep = q(sep)
local nonsep = ("[^%s]"):format(qsep)
local nonspace = ("[^%s]"):format(qsep.."%s")
if sep == MD_MARK_HEAD then
s,e,cap = string.find(tx,"^(%"..MD_MARK_HEAD..".+[%w%p])", start)
-- always search from the start of the line
-- [%w%p] set is needed to avoid continuing this markup to the next line
s,e,cap = string.find(tx,"^("..q(MD_MARK_HEAD)..".+[%w%p])")
elseif sep == MD_MARK_LINK then
local any = "[^%"..MD_MARK_LINK.."%"..MD_MARK_LSEP.."]-"
s,e,cap = string.find(tx,"^(%"..MD_MARK_LINK.."[%w%p]"..any.."[%w%p]"..
"%"..MD_MARK_LSEP.."[%w%p]"..any.."[%w%p]"..
"%"..MD_MARK_LINK..")", st)
else
s,e,cap = string.find(tx,"^(%"..sep.."[%w%p][^%"..sep.."]-[%w%p]%"..sep..")", st)
if not s then s,e,cap = string.find(tx,"^(%"..sep.."[^%s%"..sep.."]%"..sep..")", st) end
-- allow everything except spaces in the second part
s,e,cap = string.find(tx,"^("..q(MD_MARK_LINK)..nonspace..".-"..nonspace
..q(MD_MARK_LSEP).."[^%s]+"
..q(MD_MARK_LINT)..")", st)
elseif markup[sep] then
-- try 2+ characters between separators first
-- if not found, try a single character
s,e,cap = string.find(tx,"^("..qsep..nonspace..nonsep.."-"..nonspace..qsep..")", st)
if not s then s,e,cap = string.find(tx,"^("..qsep..nonspace..qsep..")", st) end
end
if s then return s,e,cap,sep end
if s and -- selected markup is surrounded by spaces or punctuation
(s == start or tx:sub(s-1, s-1):match("[%s%p]")) and
(e-s == #tx-1 or tx:sub(e+1, e+1):match("[%s%p]"))
then return s,e,cap,sep end
start = st+1
end
end
@@ -105,15 +122,14 @@ function MarkupStyle(editor, lines, linee)
-- always style to the end as there may be comments that need re-styling
-- technically, this should be GetLineCount()-1, but we want to style
-- beyond the last line to make sure it is styled correctly
local linee = linee or editor:GetLineCount()-1
local linef = editor:GetLineCount()
local linee = linee or editor:GetLineCount()
local iscomment = {}
for i,v in pairs(editor.spec.iscomment) do
iscomment[i] = v
end
for line=lines,linef do
for line=lines,linee do
local tx = editor:GetLine(line)
local ls = editor:PositionFromLine(line)
@@ -130,19 +146,20 @@ function MarkupStyle(editor, lines, linee)
local p = ls+f+off
local s = bit.band(editor:GetStyleAt(p), 31)
if iscomment[s] then
editor:StartStyling(p, 31)
editor:SetStyling(1, markup[MD_MARK_MARK].st)
local endmark = 1
local smark = #mark
local emark = #mark -- assumes end mark is the same length as start mark
if mark == MD_MARK_HEAD then
endmark = 0
-- grab multiple MD_MARK_HEAD if present
local _,_,full = string.find(w,"^("..q(MD_MARK_HEAD).."+)")
smark,emark = #full,0
elseif mark == MD_MARK_LINK then
local pipe = w:find(MD_MARK_LSEP)
if pipe then
endmark = #w-pipe+1
end
local lsep = w:find(q(MD_MARK_LSEP))
if lsep then emark = #w-lsep+#MD_MARK_LINT end
end
editor:SetStyling(t-f-endmark, markup[mark].st or markup[MD_MARK_MARK].st)
editor:SetStyling(endmark, markup[MD_MARK_MARK].st)
editor:StartStyling(p, 31)
editor:SetStyling(smark, markup[MD_MARK_MARK].st)
editor:SetStyling(t-f+1-smark-emark, markup[mark].st or markup[MD_MARK_MARK].st)
editor:SetStyling(emark, markup[MD_MARK_MARK].st)
end
off = off + t
@@ -151,3 +168,31 @@ function MarkupStyle(editor, lines, linee)
end
end
end
-- this could work by calling MarkupStyle directly from EVT_UPDATEUI,
-- but the styling didn't work correctly as the style on block comments
-- (which is used to identify where the markup should be applied)
-- was not always correct during UPDATEUI event.
-- to rectify this, we style immediately (by calling MarkupStyle
-- from UPDATEUI), but also store the starting point and re-style during
-- the next UPDATEUI/IDLE event when the block comment style is correct.
local needStyle = {}
local frame
function MarkupStyleRefresh(editor, ev)
if not frame then
frame = ide.frame
frame:Connect(wx.wxEVT_IDLE,
function(event) MarkupStyleRefresh(); event:Skip() end)
end
if not ev or #ev == 0 then -- no new records, refresh deferred ones
for ed,line in pairs(needStyle) do
MarkupStyle(ed, line)
needStyle[ed] = nil
end
else -- store records from the event table to defer refresh
for _,pos in ipairs(ev) do
needStyle[editor] = editor:LineFromPosition(pos[1])
end
end
end

View File

@@ -99,7 +99,7 @@ frame:Connect(ID "edit.showtooltip", wx.wxEVT_COMMAND_MENU_SELECTED,
local linestart = editor:PositionFromLine(line)
local localpos = pos-linestart
linetxtopos = linetx:sub(1,localpos)
local linetxtopos = linetx:sub(1,localpos)
linetxtopos = linetxtopos..")"
linetxtopos = linetxtopos:match("([a-zA-Z_0-9%.%:]+)%b()$")

View File

@@ -105,12 +105,11 @@ frame:Connect(ID_CLOSE, wx.wxEVT_COMMAND_MENU_SELECTED,
end)
frame:Connect(ID_CLOSE, wx.wxEVT_UPDATE_UI,
function (event)
event:Enable((GetEditor() ~= nil) and (debugger.server == nil))
event:Enable(GetEditor() ~= nil)
end)
frame:Connect(ID_EXIT, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
if not SaveOnExit(true) then return end
frame:Close() -- will handle wxEVT_CLOSE_WINDOW
DebuggerCloseWatchWindow()
frame:Close() -- this will trigger wxEVT_CLOSE_WINDOW
end)

View File

@@ -43,6 +43,7 @@ end
local debugTab = {
{ ID_RUN, "&Run\tF6", "Execute the current project/file" },
{ ID_RUNNOW, "Run as Scratchpad\tCtrl-F6", "Execute the current project/file and keep updating the code to see immediate results", wx.wxITEM_CHECK },
{ ID_COMPILE, "&Compile\tF7", "Test compile the Lua file" },
{ ID_START_DEBUG, "Start &Debugging\tF5", "Start a debugging session" },
{ ID_ATTACH_DEBUG, "&Start Debugger Server\tShift-F6", "Allow a client to start a debugging session" },
@@ -161,14 +162,14 @@ do
menuBar:Check(defaultid, true)
end
local function getNameToRun()
local function getNameToRun(skipcheck)
local editor = GetEditor()
-- test compile it before we run it, if successful then ask to save
-- only compile if lua api
if (editor.spec.apitype and
editor.spec.apitype == "lua" and
not CompileProgram(editor)) then
(not CompileProgram(editor, true) and not skipcheck)) then
return
end
@@ -179,7 +180,7 @@ local function getNameToRun()
return wx.wxFileName(openDocuments[id].filePath)
end
local function activateOutput()
function ActivateOutput()
if not ide.config.activateoutput then return end
-- show output/errorlog pane
uimgr:GetPane("bottomnotebook"):Show(true)
@@ -192,27 +193,40 @@ local function activateOutput()
end
local function runInterpreter(wfilename, withdebugger)
activateOutput()
ActivateOutput()
ClearAllCurrentLineMarkers()
if not wfilename then return end
local pid = ide.interpreter:frun(wfilename, withdebugger)
debugger.pid = pid
debugger.pid = ide.interpreter:frun(wfilename, withdebugger)
return debugger.pid
end
function ProjectRun()
runInterpreter(getNameToRun())
function ProjectRun(skipcheck)
local fname = getNameToRun(skipcheck)
if not fname then return end
runInterpreter(fname)
return true
end
function ProjectDebug()
local debuggers = {
debug = "require('mobdebug').loop('%s',%d)",
scratchpad = "require('mobdebug').scratchpad('%s',%d)"
}
function ProjectDebug(skipcheck, debtype)
if (debugger.server ~= nil) then
if (not debugger.running) then
ClearAllCurrentLineMarkers()
debugger.run()
end
else
runInterpreter(getNameToRun(), true)
local debcall = (debuggers[debtype or "debug"]):
format(wx.wxGetHostName(), ide.debugger.portnumber)
local fname = getNameToRun(skipcheck)
if not fname then return end
runInterpreter(fname, debcall)
end
return true
end
-----------------------
@@ -233,7 +247,7 @@ frame:Connect(ID_TOGGLEBREAKPOINT, wx.wxEVT_UPDATE_UI,
frame:Connect(ID_COMPILE, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
local editor = GetEditor()
activateOutput()
ActivateOutput()
CompileProgram(editor)
end)
frame:Connect(ID_COMPILE, wx.wxEVT_UPDATE_UI,
@@ -242,13 +256,30 @@ frame:Connect(ID_COMPILE, wx.wxEVT_UPDATE_UI,
event:Enable((debugger.server == nil and debugger.pid == nil) and (editor ~= nil))
end)
frame:Connect(ID_RUN, wx.wxEVT_COMMAND_MENU_SELECTED, ProjectRun)
frame:Connect(ID_RUN, wx.wxEVT_COMMAND_MENU_SELECTED, function () ProjectRun() end)
frame:Connect(ID_RUN, wx.wxEVT_UPDATE_UI,
function (event)
local editor = GetEditor()
event:Enable((debugger.server == nil and debugger.pid == nil) and (editor ~= nil))
end)
frame:Connect(ID_RUNNOW, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
if event:IsChecked() then
if not DebuggerScratchpadOn(GetEditor()) then
menuBar:Check(ID_RUNNOW, false) -- disable if couldn't start scratchpad
end
else DebuggerScratchpadOff() end
end)
frame:Connect(ID_RUNNOW, wx.wxEVT_UPDATE_UI,
function (event)
local editor = GetEditor()
-- allow scratchpad if there is no server or there is a server, but it is
-- allowed to turn it into a scratchpad and we are not debugging anything
event:Enable((editor ~= nil) and ((debugger.server == nil or debugger.scratchable)
and debugger.pid == nil or debugger.scratchpad ~= nil))
end)
frame:Connect(ID_ATTACH_DEBUG, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
ClearAllCurrentLineMarkers()
@@ -257,16 +288,19 @@ frame:Connect(ID_ATTACH_DEBUG, wx.wxEVT_COMMAND_MENU_SELECTED,
frame:Connect(ID_ATTACH_DEBUG, wx.wxEVT_UPDATE_UI,
function (event)
local editor = GetEditor()
event:Enable((ide.interpreter) and (ide.interpreter.fattachdebug) and
(not debugger.listening) and (debugger.server == nil) and (editor ~= nil))
event:Enable((ide.interpreter) and (ide.interpreter.fattachdebug)
and (not debugger.listening) and (debugger.server == nil)
and (editor ~= nil) and (not debugger.scratchpad))
end)
frame:Connect(ID_START_DEBUG, wx.wxEVT_COMMAND_MENU_SELECTED, ProjectDebug)
frame:Connect(ID_START_DEBUG, wx.wxEVT_COMMAND_MENU_SELECTED, function () ProjectDebug() end)
frame:Connect(ID_START_DEBUG, wx.wxEVT_UPDATE_UI,
function (event)
local editor = GetEditor()
event:Enable((ide.interpreter) and (ide.interpreter.hasdebugger) and
((debugger.server == nil and debugger.pid == nil) or (debugger.server ~= nil and not debugger.running)) and (editor ~= nil))
((debugger.server == nil and debugger.pid == nil) or
(debugger.server ~= nil and not debugger.running)) and
(editor ~= nil) and (not debugger.scratchpad))
local label = (debugger.server ~= nil)
and debugMenuRun.continue or debugMenuRun.start
if debugMenu:GetLabel(ID_START_DEBUG) ~= label then
@@ -299,7 +333,8 @@ frame:Connect(ID_STEP, wx.wxEVT_COMMAND_MENU_SELECTED,
frame:Connect(ID_STEP, wx.wxEVT_UPDATE_UI,
function (event)
local editor = GetEditor()
event:Enable((debugger.server ~= nil) and (not debugger.running) and (editor ~= nil))
event:Enable((debugger.server ~= nil) and (not debugger.running)
and (editor ~= nil) and (not debugger.scratchpad))
end)
frame:Connect(ID_STEP_OVER, wx.wxEVT_COMMAND_MENU_SELECTED,
@@ -310,7 +345,8 @@ frame:Connect(ID_STEP_OVER, wx.wxEVT_COMMAND_MENU_SELECTED,
frame:Connect(ID_STEP_OVER, wx.wxEVT_UPDATE_UI,
function (event)
local editor = GetEditor()
event:Enable((debugger.server ~= nil) and (not debugger.running) and (editor ~= nil))
event:Enable((debugger.server ~= nil) and (not debugger.running)
and (editor ~= nil) and (not debugger.scratchpad))
end)
frame:Connect(ID_STEP_OUT, wx.wxEVT_COMMAND_MENU_SELECTED,
@@ -321,7 +357,8 @@ frame:Connect(ID_STEP_OUT, wx.wxEVT_COMMAND_MENU_SELECTED,
frame:Connect(ID_STEP_OUT, wx.wxEVT_UPDATE_UI,
function (event)
local editor = GetEditor()
event:Enable((debugger.server ~= nil) and (not debugger.running) and (editor ~= nil))
event:Enable((debugger.server ~= nil) and (not debugger.running)
and (editor ~= nil) and (not debugger.scratchpad))
end)
frame:Connect(ID_TRACE, wx.wxEVT_COMMAND_MENU_SELECTED,
@@ -332,7 +369,8 @@ frame:Connect(ID_TRACE, wx.wxEVT_COMMAND_MENU_SELECTED,
frame:Connect(ID_TRACE, wx.wxEVT_UPDATE_UI,
function (event)
local editor = GetEditor()
event:Enable((debugger.server ~= nil) and (not debugger.running) and (editor ~= nil))
event:Enable((debugger.server ~= nil) and (not debugger.running)
and (editor ~= nil) and (not debugger.scratchpad))
end)
frame:Connect(ID_BREAK, wx.wxEVT_COMMAND_MENU_SELECTED,
@@ -344,7 +382,8 @@ frame:Connect(ID_BREAK, wx.wxEVT_COMMAND_MENU_SELECTED,
frame:Connect(ID_BREAK, wx.wxEVT_UPDATE_UI,
function (event)
local editor = GetEditor()
event:Enable((debugger.server ~= nil) and (debugger.running) and (editor ~= nil))
event:Enable((debugger.server ~= nil) and (debugger.running)
and (editor ~= nil) and (not debugger.scratchpad))
end)
--[[
@@ -362,7 +401,7 @@ frame:Connect(ID "view.debug.callstack", wx.wxEVT_UPDATE_UI,
frame:Connect(wx.wxEVT_IDLE,
function(event)
if (debugger.update) then
debugger.update()
end
if (debugger.update) then debugger.update() end
if (debugger.scratchpad) then DebuggerRefreshScratchpad() end
event:Skip() -- let other EVT_IDLE handlers to work on the event
end)

View File

@@ -19,7 +19,7 @@ errorlog:MarkerDefine(CURRENT_LINE_MARKER, wxstc.wxSTC_MARK_ARROWS, wx.wxBLACK,
errorlog:SetReadOnly(true)
StylesApplyToEditor(ide.config.stylesoutshell,errorlog,ide.ofont,ide.ofontItalic)
function ClearOutput(event)
function ClearOutput()
errorlog:SetReadOnly(false)
errorlog:ClearAll()
errorlog:SetReadOnly(true)
@@ -71,7 +71,9 @@ end
pcall(function () return require 'winapi' end)
local pid = nil
local function unHideWxWindow(pidAssign)
if pidAssign ~= nil then
-- skip if not configured to do anything
if not ide.config.unhidewxwindow then return end
if pidAssign then
pid = pidAssign > 0 and pidAssign or nil
end
if pid and winapi then
@@ -81,6 +83,7 @@ local function unHideWxWindow(pidAssign)
end)
if win and not win:is_visible() then
win:show()
notebook:SetFocus() -- set focus back to the IDE window
pid = nil
end
end
@@ -188,6 +191,7 @@ errorlog:Connect(wx.wxEVT_END_PROCESS, function(event)
end
customprocs[pid] = nil
unHideWxWindow(0)
DebuggerStop()
DisplayOutput("Program finished (pid: "..pid..").\n")
end
end)

View File

@@ -248,6 +248,7 @@ local function executeShellCode(tx)
fn,err = loadstring(tx)
-- for statement queries create the return
if err and (err:find("'=' expected near '<eof>'") or
err:find("syntax error near '") or
err:find("unexpected symbol near '")) then
local errmore
fn,errmore = loadstring("return "..tx:gsub("^%s*=%s*",""))

View File

@@ -44,6 +44,7 @@ ide = {
},
activateoutput = false, -- activate output/console on Run/Debug/Compile
unhidewxwindow = false, -- try to unhide a wx window
filehistorylength = 20,
projecthistorylength = 15,
savebak = false,

View File

@@ -1,9 +1,11 @@
LICENSE
README
README.md
api/lua/baselib.lua
api/readme.txt
bin/clibs/lfs.dll
bin/clibs/mojoshader.dll
bin/clibs/mime/core.dll
bin/clibs/ssl.dll
bin/clibs/socket/core.dll
bin/clibs/wx/wxlua_msw28_wxbindadv.dll
bin/clibs/wx/wxlua_msw28_wxbindaui.dll
@@ -42,13 +44,20 @@ cfg/user.lua_for_custom_settings.txt
interpreters/luadeb.lua
lualibs/copas/copas.lua
lualibs/coxpcall/coxpcall.lua
lualibs/luainspect/ast.lua
lualibs/luainspect/compat_env.lua
lualibs/luainspect/dump.lua
lualibs/luainspect/globals.lua
lualibs/luainspect/init.lua
lualibs/luainspect/signatures.lua
lualibs/luainspect/typecheck.lua
lualibs/luainspect/types.lua
lualibs/metalua/compile.lua
lualibs/metalua/gg.lua
lualibs/metalua/lcode.lua
lualibs/metalua/ldump.lua
lualibs/metalua/lexer.lua
lualibs/metalua/lopcodes.lua
lualibs/metalua/metalua
lualibs/metalua/metalua.lua
lualibs/metalua/mlp_expr.lua
lualibs/metalua/mlp_ext.lua
@@ -62,14 +71,16 @@ lualibs/metalua/metalua/runtime.lua
lualibs/metalua/metalua/string2.lua
lualibs/metalua/metalua/table2.lua
lualibs/mobdebug/mobdebug.lua
lualibs/socket/ltn12.lua
lualibs/socket/mime.lua
lualibs/socket/socket.lua
lualibs/socket/socket/ftp.lua
lualibs/socket/socket/http.lua
lualibs/socket/socket/smtp.lua
lualibs/socket/socket/tp.lua
lualibs/socket/socket/url.lua
lualibs/ltn12.lua
lualibs/mime.lua
lualibs/socket.lua
lualibs/socket/ftp.lua
lualibs/socket/http.lua
lualibs/socket/smtp.lua
lualibs/socket/tp.lua
lualibs/socket/url.lua
lualibs/ssl.lua
lualibs/ssl/https.lua
spec/lua.lua
src/defs.lua
src/editor/autocomplete.lua
@@ -80,6 +91,7 @@ src/editor/filetree.lua
src/editor/findreplace.lua
src/editor/gui.lua
src/editor/ids.lua
src/editor/inspect.lua
src/editor/iofilters.lua
src/editor/markup.lua
src/editor/menu.lua

View File

@@ -25,7 +25,8 @@ local app = {
end
wx.wxArtProvider.Push(artProvider)
ide.config.interpreter = "luadeb";
ide.config.interpreter = "luadeb"
ide.config.unhidewxwindow = true
-- this needs to be in pre-init to load the styles
dofile("src/editor/markup.lua")
@@ -33,6 +34,7 @@ local app = {
postinit = function ()
dofile("zbstudio/menu_help.lua")
dofile("src/editor/inspect.lua")
local bundle = wx.wxIconBundle()
local files = FileSysGet("zbstudio/res/", wx.wxFILE)

View File

@@ -6,6 +6,7 @@ local ide = ide
local frame = ide.frame
local menuBar = frame.menuBar
local mobdebug = require "mobdebug"
local helpMenu = wx.wxMenu{
{ ID_ABOUT, "&About\tF1", "About ZeroBrane Studio" },