Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1961e5c4b | ||
|
|
3ead2966d8 | ||
|
|
0bcf590f45 | ||
|
|
e8f308ca08 | ||
|
|
ecb59ceb9e | ||
|
|
a317f986e1 | ||
|
|
623905c81c | ||
|
|
11b702ac6e | ||
|
|
f926aef2f8 | ||
|
|
3754406e85 | ||
|
|
981bccbe27 | ||
|
|
047a9e8ac5 | ||
|
|
d1f2e8450a | ||
|
|
21cfba9cb6 | ||
|
|
3f711373ed | ||
|
|
00fe05e89f | ||
|
|
3bcd54bd46 | ||
|
|
8de9e41fd6 | ||
|
|
29b6755a9b | ||
|
|
7d6be282f1 | ||
|
|
f89fd4d752 | ||
|
|
03adda1cef | ||
|
|
2992424e87 | ||
|
|
e302a97682 | ||
|
|
7b5d37d595 | ||
|
|
01ae85dc5a | ||
|
|
f650d8f64f | ||
|
|
602f8ef223 | ||
|
|
6afc999b75 | ||
|
|
2ccd214a1f | ||
|
|
8efde0ec1f | ||
|
|
1c5b14870c | ||
|
|
2c87909920 | ||
|
|
2a2a3bed96 | ||
|
|
e2f65bced5 | ||
|
|
c979d60d28 | ||
|
|
7954ff1f64 | ||
|
|
a3a5c75694 | ||
|
|
d6f3b4052b | ||
|
|
7bc64d90c7 | ||
|
|
56d262b753 | ||
|
|
a368f0acf9 | ||
|
|
d4a53733e7 | ||
|
|
1117e9df9a | ||
|
|
230de3450f | ||
|
|
0ae48ce6de | ||
|
|
6a90c5e850 | ||
|
|
f1ac72b265 | ||
|
|
79fd90c986 | ||
|
|
4dcf470c09 | ||
|
|
fcdbd456de | ||
|
|
e92127d6c8 | ||
|
|
0d8e6b0581 | ||
|
|
16d72396b1 | ||
|
|
6c7e289f71 | ||
|
|
deb99ec084 | ||
|
|
12f84d3509 |
@@ -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
BIN
bin/clibs/mime/core.dll
Normal file
Binary file not shown.
BIN
bin/clibs/ssl.dll
Normal file
BIN
bin/clibs/ssl.dll
Normal file
Binary file not shown.
@@ -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
929
lualibs/luainspect/ast.lua
Normal 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
|
||||
390
lualibs/luainspect/compat_env.lua
Normal file
390
lualibs/luainspect/compat_env.lua
Normal 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
|
||||
--]]
|
||||
|
||||
90
lualibs/luainspect/dump.lua
Normal file
90
lualibs/luainspect/dump.lua
Normal 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
|
||||
|
||||
222
lualibs/luainspect/globals.lua
Normal file
222
lualibs/luainspect/globals.lua
Normal 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
1431
lualibs/luainspect/init.lua
Normal file
File diff suppressed because it is too large
Load Diff
433
lualibs/luainspect/signatures.lua
Normal file
433
lualibs/luainspect/signatures.lua
Normal 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
|
||||
40
lualibs/luainspect/typecheck.lua
Normal file
40
lualibs/luainspect/typecheck.lua
Normal 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
|
||||
130
lualibs/luainspect/types.lua
Normal file
130
lualibs/luainspect/types.lua
Normal 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
|
||||
@@ -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
93
lualibs/ssl.lua
Normal 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
138
lualibs/ssl/https.lua
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
150
src/editor/inspect.lua
Normal 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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()$")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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*",""))
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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" },
|
||||
|
||||
Reference in New Issue
Block a user