Compare commits

...

187 Commits
0.26 ... 0.31

Author SHA1 Message Date
Paul Kulchenko
7b2241085d Added refresh of filetree on MacOS to get it displayed correctly when the app is started 2012-07-14 11:45:57 -07:00
Paul Kulchenko
c9cfd42d34 Corrected resetting of project directory when it's already set and doesn't need to be changed 2012-07-13 23:30:13 -07:00
Paul Kulchenko
c25c56069d Added handling of balanced brackets in markup links 2012-07-13 22:43:27 -07:00
Paul Kulchenko
a68e8f7bd3 Added unit test module 2012-07-13 22:42:35 -07:00
Paul Kulchenko
fc6de036e0 Reorganized handling of font configuration and added font config for filetree (with a different size default on MacOS) 2012-07-13 09:54:52 -07:00
Paul Kulchenko
08d42a2501 Removed setting the editor font in the config as the default font is different on different platforms 2012-07-12 22:07:10 -07:00
Paul Kulchenko
da32878984 Added reporting the number of traced lines during debugging 2012-07-12 14:21:56 -07:00
Paul Kulchenko
3fd5d88656 Reset project directory if the current one doesn't exist 2012-07-11 23:16:33 -07:00
Paul Kulchenko
7d9ad5100c Fixed markup styling and file tree drawing on MacOS 2012-07-11 16:11:11 -07:00
Paul Kulchenko
6bff634446 Added explicit calls to CreateBitmap as wxArtProvider.GetBitmap doesn't alway call a custom art provider on MacOS (2.8.12) and in some cases on msWin (2.8.7) 2012-07-11 13:24:45 -07:00
Paul Kulchenko
b0de487bf0 Fixed detecting executable name in commands with spaces 2012-07-11 13:21:22 -07:00
Paul Kulchenko
560d56835a Fixed typo 2012-07-11 13:20:35 -07:00
Paul Kulchenko
3c6a06f537 Added setting of PATH and CPATH to find proper libs on windows and mac os platforms 2012-07-11 11:24:33 -07:00
Paul Kulchenko
b1f3bf0bd9 Fixed incorrect folders reported in the file tree when no project directory is set and a file is open 2012-07-10 23:32:38 -07:00
Paul Kulchenko
6ea3a4708d Fixed incorrect filename reported in compile errors when the file is not saved 2012-07-10 22:31:06 -07:00
Paul Kulchenko
4de0eb1dc0 Made 'View Stack Window' and 'View Watch Window' refresh window content if it's already shown 2012-07-07 15:08:58 -07:00
Paul Kulchenko
c2f6ed6338 Removed extension from the template to match folders to make it more portable 2012-07-07 11:49:30 -07:00
Paul Kulchenko
fad7ff0cc3 Updated matching of links to make them less greedy (to avoid capturing link terminators) 2012-07-07 11:46:41 -07:00
Paul Kulchenko
9afa35f9b2 Upgraded deprecated constants and logic for compatibility with wxwidgets 2.9.x 2012-07-06 20:54:11 -07:00
Paul Kulchenko
3a5b46f65e Fixed an error thrown when a window with debugging is closed before the application being debugged is terminated 2012-07-06 20:51:01 -07:00
Paul Kulchenko
26d0908c89 Added scratchpad support for love2d 2012-07-03 13:00:18 -07:00
Paul Kulchenko
770db579d9 Added reset of 'modified' status to keep tab names and their config settings correct upon exit 2012-07-03 12:47:18 -07:00
Paul Kulchenko
8e09ed1658 Fixed incorrect storing of settings for editor tabs with the same text (filename). This was causing only one tab displayed for multiple StyledText controls with interesting effects 2012-07-02 11:53:24 -07:00
Paul Kulchenko
25ac96d39a Removed styling of function calls and capturing definitions in strings and comments (fixed #18) 2012-07-01 22:11:03 -07:00
Paul Kulchenko
22fda661ec Updated matching logic for function definitions to allow for a.b.c() definitions (fixes #17) 2012-07-01 21:59:13 -07:00
Paul Kulchenko
529a0e8c9e Added window title update and filetree refresh after SaveAs command 2012-07-01 15:13:12 -07:00
Paul Kulchenko
7cc061e18d Fixed an issue with launching a process when its output is not redirected to the IDE (fixes #16). Added callback for all started processes to make them call OnTerminate event upon completion 2012-07-01 09:44:00 -07:00
Paul Kulchenko
5f2226bac2 Fixed console to evaluate 'function a() ... end' without errors 2012-06-30 11:12:30 -07:00
Paul Kulchenko
37795c2480 Added tooltip to display variable/expression values during debugging 2012-06-30 11:04:48 -07:00
Paul Kulchenko
5e934dfd69 Added checks around ShowFullScreen() calls to avoid failures on those systems that don't provide it (linux/GTK) 2012-06-29 23:39:53 -07:00
Paul Kulchenko
f970ba38a0 Removed setting focus to the Output window when output is processed as it interfered with Run as Scratchpad 2012-06-29 23:35:35 -07:00
Paul Kulchenko
8af6c1b5b6 Fixed a compilation error caused by shebang in scripts 2012-06-29 16:58:25 -07:00
Paul Kulchenko
0777a5fbd8 Added check for debugger calls to avoid errors when debugger is not loaded 2012-06-29 15:31:09 -07:00
Paul Kulchenko
942681e246 Fixed an issue with love2d path with spaces 2012-06-28 20:05:35 -07:00
Paul Kulchenko
ce8f120e48 Improved logic in love2d integration to distinguish Debug and Run commands
(closes #13)
2012-06-28 10:24:13 -07:00
Paul Kulchenko
048fd2349a Updated menus to avoid conflicts with MacOS shortcuts 2012-06-26 22:13:52 -07:00
Paul Kulchenko
2627957e5a Updated logic creating menubar to make it work correctly on MacOS with special Help/About items 2012-06-26 21:32:06 -07:00
Paul Kulchenko
16b0ca0fc7 Updated path handling to better detect how the app is started and to avoid loading dlls on non-windows platforms 2012-06-26 19:29:00 -07:00
Paul Kulchenko
e3189ba38a Updated logic for detecting hostname (used in the debugger) to make sure it is resolvable 2012-06-26 19:11:39 -07:00
Paul Kulchenko
ad48622f68 Fixed aborting running/debugged programs on MacOS by adding MAKE_GROUP_LEADER option to wxExecute 2012-06-26 19:06:14 -07:00
Paul Kulchenko
0a9439ad22 Added display of hierarchical data in Stack window 2012-06-25 18:23:26 -07:00
Paul Kulchenko
a7577d5054 Updated Analyze output 2012-06-25 18:22:20 -07:00
Paul Kulchenko
668cdcb4db Added auto complete for love2d API 2012-06-25 14:47:21 -07:00
Paul Kulchenko
4ec3e87473 Added support for debugging processes running under LuaJIT (requires MobDebug v0.467+) 2012-06-24 22:01:02 -07:00
Paul Kulchenko
90b4f38223 Added love2d support 2012-06-24 17:52:59 -07:00
Paul Kulchenko
c1918e5c38 Removed extensions from launch commands and updated display logic in the Output window 2012-06-24 16:40:10 -07:00
Paul Kulchenko
34baaa9447 Fixed an issue in the logic for setting breakpoints, which ignored breakpoints in luxinia2 debug sessions 2012-06-24 16:00:25 -07:00
Paul Kulchenko
817938593a Fixed logic in the local/remote console that returned incorrect error message on executing code like '%s':format(1); expressions are evaluated first now and the order of evaluation is consistent between local and remote consoles 2012-06-23 23:55:59 -07:00
Paul Kulchenko
b944f9d01d Fixed IDs for Project menu items to allow them to be removed from the menu if needed 2012-06-23 23:53:34 -07:00
Paul Kulchenko
5d4b2395b5 Added execution time and updated messages in the Output window to be more consistent 2012-06-23 13:33:29 -07:00
Paul Kulchenko
f9ab1546ff Added displaying 'nil' values in local console when no result is returned by an expression 2012-06-20 21:36:04 -07:00
Paul Kulchenko
971e7c8316 Disabled Stack and Watch updates when scratchpad is active as they interfere with application execution 2012-06-20 21:30:42 -07:00
Paul Kulchenko
60a89b8f32 Fixed an app running without debugger when 'stack' command is sent immediately after an external application connects to the debugger 2012-06-20 19:21:25 -07:00
Paul Kulchenko
7275f0d11a Fixed an issue with remote application not terminating when IDE is closed while debugging is in progress 2012-06-20 18:19:40 -07:00
Paul Kulchenko
35213d7d3b Added a check to refuse starting a new debugging session if there is one in progress already 2012-06-20 17:02:05 -07:00
Paul Kulchenko
cde26253e3 Fixed sending breakpoints set before establishing a debugging session started from another process that connects to the IDE debugger 2012-06-20 16:54:50 -07:00
Paul Kulchenko
b45c0600c7 Added handling of tail calls in the Stack window 2012-06-20 16:33:24 -07:00
Paul Kulchenko
5784dea99a Fixed refreshing a modified file when the editor is set to read-only mode 2012-06-19 12:24:06 -07:00
Paul Kulchenko
e880fdde60 Added pretty printing in Watch and Console (local and remote) windows and handling of multiple results in Console 2012-06-19 09:34:32 -07:00
Paul Kulchenko
b7d8512b7b Added Stack window to display stack information and local/upvalue values for each stack frame 2012-06-18 14:53:10 -07:00
Paul Kulchenko
a7a8f32c91 Fixed saving/restoring configuration of 'Output'/'Console' tabs when IDE is closed while debugging is in progress 2012-06-16 17:31:21 -07:00
Paul Kulchenko
9fc4ab5e9b Fixed removing variable name in Watch window after escaping editing 2012-06-15 16:39:51 -07:00
Paul Kulchenko
37434534cc Fixed #9 as it had incorrect logic in one of UTF filters 2012-06-11 08:24:27 -07:00
Paul Kulchenko
fc03c2843b Fixed ability to set font encoding for 'Output' and 'Console' windows in the config 2012-06-10 16:01:24 -07:00
Paul Kulchenko
2abbad04ac Added ability to set font encoding in the config 2012-06-10 15:40:14 -07:00
Paul Kulchenko
dbb392e009 Moved processing of user.lua to a later phase after tools and specs are
already loaded to allow modification of IDE configuration from `user.lua`.
Closes #5.

Added example to user-sample.lua on how the configuration can be modified.
2012-06-09 18:57:11 -07:00
Paul Kulchenko
65947ff924 Added checks to prevent text modification in 'Output' and 'Console'
windows. Fixes #8.

Text modifications are checked against (1) direct input, (2) actions using
context menu, and (3) move/copy using selection and mouse drag and drop.
Added input marker to the 'Output' window to indicate where input is
expected.
2012-06-09 17:17:24 -07:00
Paul Kulchenko
d8324b269d Fixed edit menu shortcuts to work in the 'Output' window (when allowed) 2012-06-08 16:44:17 -07:00
Paul Kulchenko
ac9c2e9a84 Added ability to interact with scripts by allowing text to be entered in the 'Output' window.
This is controlled by ide.config.allowinteractivescript variable".
 The window is only activated and switched into read-write mode after
 there is some output to it.
2012-06-08 13:07:48 -07:00
Paul Kulchenko
3b2fcfd1f9 Added restoring cursor position when a modified file is reloaded in the editor; removed unnecessary isFileAlteredOnDisk check 2012-06-08 13:01:44 -07:00
Paul Kulchenko
14fe7af771 Fixed reporting of processes that failed to start after 'Run' or 'Debug' commands 2012-06-07 14:13:17 -07:00
Paul Kulchenko
7b0405bc79 Fixed executable path matching to work on systems that don't have file extensions 2012-06-07 13:52:50 -07:00
Paul Kulchenko
fc6b0c176d Cleaned up ShellSupportRemote interface to remove a parameter not being used 2012-06-07 13:51:20 -07:00
Paul Kulchenko
5c50a2645b Improved reporting in static analysis for functions and global variables.
Added "unknown global function...".
Added "unused local function...".
Added "local function ... masks...".
Updated reporting of unused global to "first use of unknown global" to
minimise the number of instances reported until globals assigned through
"required" modules are recognized.
2012-06-05 16:15:02 -07:00
Paul Kulchenko
11cfbfa68d Fixed localization of variables in several places 2012-06-05 15:37:30 -07:00
Paul Kulchenko
e6b318c643 Fixed #3 'unused parameter...' check not to fail on anonymous functions that are part of an expression 2012-06-05 14:06:40 -07:00
Paul Kulchenko
1351db6114 Several fixes to the static code analyzer.
"Unknown global" is only reported once per line.
"First assignment to global..." is fixed to only report when the variable
is on the left side of the assignment.
"Value discarded" warning is added when multiple assignment has more values than
variables.
2012-06-05 11:06:46 -07:00
Paul Kulchenko
b58e931409 Changed static analyzer to only load metalua when Analyze function is
used, rather than on startup.
2012-06-04 12:21:47 -07:00
Paul Kulchenko
e0eb05b122 Disabled 'Run as Scratchpad' if there is no debugger registered capable of running it 2012-06-04 12:15:14 -07:00
Paul Kulchenko
8ecd8dfa6b Changed order of lualibs/ and bin/ directories in package.path and
package.cpath to load included modules first.

This should minimize conflicts with other versions of same modules that
may exist somewhere else in the path.
2012-06-04 12:01:10 -07:00
Paul Kulchenko
c1961e5c4b Added Ctrl(-Shift)-TAB navigation between tabs in the editor 2012-05-31 14:20:48 -07:00
Paul Kulchenko
3ead2966d8 Fixed localization of variables based on static analysis 2012-05-30 13:15:58 -07:00
Paul Kulchenko
0bcf590f45 Added reporting of assignment to global variables in the code analyzer 2012-05-30 11:27:17 -07:00
Paul Kulchenko
e8f308ca08 Added ability to turn external processes that connect to debugger into a scratchpad; fixed 'exit' to make it work for remote applications; upgraded to MobDebug 0.449 2012-05-26 23:02:20 -07:00
Paul Kulchenko
ecb59ceb9e Updated comment styling to follow markdown syntax 2012-05-15 23:05:26 -07:00
Paul Kulchenko
a317f986e1 Fixed styling in markup module that wasn't always working when a block comment was uncommented 2012-05-14 19:01:40 -07:00
Paul Kulchenko
623905c81c Added navigation between editor tabs using Ctrl-PgUp and Ctrl-PgDn 2012-05-13 23:10:25 -07:00
Paul Kulchenko
11b702ac6e Added exit from full screen mode using ESC key 2012-05-13 22:38:17 -07:00
Paul Kulchenko
f926aef2f8 Updated MANIFEST with luainspect module files and luasocket module reorg 2012-05-13 22:36:12 -07:00
Paul Kulchenko
3754406e85 Merge branch 'scratchpad' 2012-05-11 16:39:21 -07:00
Paul Kulchenko
981bccbe27 Added menu shortcut for Run As Scratchpad 2012-05-11 14:56:35 -07:00
Paul Kulchenko
047a9e8ac5 Upgraded to Mobdebug 0.448 to fix handling of scripts with comments in the remote shell 2012-05-11 14:30:58 -07:00
Paul Kulchenko
d1f2e8450a Fixed an issue with Analyze process when the analyzed script has compilation errors 2012-05-07 23:01:31 -07:00
Paul Kulchenko
21cfba9cb6 Added reporting of compilation errors during debugging sessions (when external client connects to the debugger and the current script has compilation errors) 2012-05-07 22:56:30 -07:00
Paul Kulchenko
3f711373ed Added handling of more error in the shell to allow calculations like '(1+2)' to be executed correctly (both locally and remotely) 2012-05-07 22:25:22 -07:00
Paul Kulchenko
00fe05e89f Fixed an earlier introduced bug with Run/Debug proceeding after failed compilation; removed references to internal code in compilation errors (in scratchpad) 2012-05-06 18:27:15 -07:00
Paul Kulchenko
3bcd54bd46 Added moving focus back to the notebook after unhiding/activating a wx window 2012-05-06 15:28:16 -07:00
Paul Kulchenko
8de9e41fd6 Updated README 2012-05-06 14:34:45 -07:00
Paul Kulchenko
29b6755a9b Fixed cursor to change to a default one (instead of an arrow) after number sliding 2012-05-06 14:29:49 -07:00
Paul Kulchenko
7d6be282f1 Updated clearing 'output' window to minimize showing duplicate output when running as scratchpad 2012-05-06 14:20:59 -07:00
Paul Kulchenko
f89fd4d752 Added handling of negative numbers for number sliders in scratchpad; added logic to ignore malformed numbers (they are still styled though) 2012-05-05 23:11:22 -07:00
Paul Kulchenko
03adda1cef Added stopping debugger and scratchpad when a window with debugging/scratchpad action is closed 2012-05-05 18:42:12 -07:00
Paul Kulchenko
2992424e87 Fixed an issue with scratchpad being on after Save dialog is canceled 2012-05-05 17:16:24 -07:00
Paul Kulchenko
e302a97682 Added support for using different debugger calls and moved scratchpad to a different debugger for performance reasons; upgraded to Mobdebug 0.446 2012-05-05 14:20:55 -07:00
Paul Kulchenko
7b5d37d595 Added mouse capture for slider operations in scratchpad to increase the range of available values 2012-05-03 16:40:47 -07:00
Paul Kulchenko
01ae85dc5a Reorganized the scratchpad code and added scratchpad teardown code to run when the debugged application is closed 2012-05-03 16:27:54 -07:00
Paul Kulchenko
f650d8f64f Added clearing output window when requested and formatting for compile/execution error messages 2012-05-03 12:58:39 -07:00
Paul Kulchenko
602f8ef223 Fixed debugging menu items to be hidden when running as scratchpad 2012-05-03 12:22:03 -07:00
Paul Kulchenko
6afc999b75 Added number slider processing with dynamic reloading of scratchpad scripts; upgraded to MobDebug 0.445 for better stability 2012-05-03 10:25:25 -07:00
Paul Kulchenko
2ccd214a1f Added stopping criteria to allow execution of any scripts (even empty ones) under 'Run as Scratchpad'; removed stack trace from remote error messages in the scratchpad 2012-05-02 10:45:59 -07:00
Paul Kulchenko
8efde0ec1f Added a check to turn 'Run as Scratchpad' on even when the initial compilation fails 2012-05-02 10:16:30 -07:00
Paul Kulchenko
1c5b14870c Updated event logic in the scratchpad to make sure editor change events do not get lost while an earlier change is being worked on 2012-05-02 00:09:48 -07:00
Paul Kulchenko
2c87909920 Disabled 'Run as Scratchpad' option when the script is already running 2012-05-01 22:00:46 -07:00
Paul Kulchenko
2a2a3bed96 Added reporting of run-time errors when running as scratchpad; upgraded to MobDebug 0.444 2012-05-01 21:49:45 -07:00
Paul Kulchenko
e2f65bced5 Fixed an issue with frequent modifications in a scratchpad; added safety check to avoid reloads from loaded modules (like wx) 2012-04-30 13:49:33 -07:00
Paul Kulchenko
c979d60d28 Moved the logic to disable checkbox for scratchpad menu to IDLE event as EVT_UPDATE_UI is only executed when the user opens up the menu (menu is updated) 2012-04-29 23:08:29 -07:00
Paul Kulchenko
7954ff1f64 Fixed about screen 2012-04-29 21:57:47 -07:00
Paul Kulchenko
a3a5c75694 Reorganized scratchpad code to have all its logic in one place 2012-04-29 18:41:23 -07:00
Paul Kulchenko
d6f3b4052b Reimplemented scratchpad functionality using internal processes (similar to how the debugger is done) as IDE was too unstable when running in the same process 2012-04-29 17:26:22 -07:00
Paul Kulchenko
7bc64d90c7 Removed 'error during pre-compilation' message from compile errors 2012-04-28 15:11:42 -07:00
Paul Kulchenko
56d262b753 Added stopping the debugger when a debugged program exits 2012-04-27 16:33:56 -07:00
Paul Kulchenko
a368f0acf9 Merge branch 'master' of github.com:pkulchenko/ZeroBraneStudio 2012-04-23 09:32:45 -07:00
Paul Kulchenko
d4a53733e7 Added to static analysis reporting of unused parameters in functions (the function name is also reported when possible) 2012-04-23 09:16:13 -07:00
Paul Kulchenko
1117e9df9a Disabled warning in static analysis about unused 'self' in methods 2012-04-22 23:31:58 -07:00
Paul Kulchenko
230de3450f Cleaned up warnings based on results of static analysis 2012-04-22 23:29:32 -07:00
Paul Kulchenko
0ae48ce6de Cleaned up warnings based on results of static analysis 2012-04-22 22:04:23 -07:00
Paul Kulchenko
6a90c5e850 Turned 'unhiding' wx window while running or debugging off by default as it was causing performance problems with some applications 2012-04-22 15:39:03 -07:00
Paul Kulchenko
f1ac72b265 Added scratchpad (running live) functionality 2012-04-21 22:29:21 -07:00
Paul Kulchenko
79fd90c986 Removed logic to update project directory based on file selection in the filetree 2012-04-21 22:26:15 -07:00
Paul Kulchenko
4dcf470c09 Fixed an issue with menu search to insert a new item 2012-04-20 22:25:14 -07:00
Paul Kulchenko
fcdbd456de Added code analyzer based on lua-inspect 2012-04-20 14:47:31 -07:00
Paul Kulchenko
e92127d6c8 Added luainspect library (https://github.com/davidm/lua-inspect) 2012-04-20 14:36:38 -07:00
Paul Kulchenko
0d8e6b0581 Added LuaSec (including binary Windows package from http://www.inf.puc-rio.br/~brunoos/luasec/) to provide SSL/HTTPS support for the socket library (needed for gist/github integration) 2012-04-19 18:17:48 -07:00
Paul Kulchenko
16d72396b1 Added missing mime/code.dll and reorganized socket module files (socket.*) to load correctly with require 2012-04-07 22:40:55 -07:00
Paul Kulchenko
6c7e289f71 Merge pull request #1 from dkulchenko/master
Format README with Markdown
2012-04-07 22:26:03 -07:00
Daniil Kulchenko
deb99ec084 Formatting improvements. 2012-03-27 14:08:36 -07:00
Daniil Kulchenko
12f84d3509 Switch README to Markdown. 2012-03-27 13:58:19 -07:00
Paul Kulchenko
3a18136076 Fixed 'Trace' command to continue working when a debugged file is not activated 2012-03-21 10:16:17 -07:00
Paul Kulchenko
1b057988bc Updated to work with mobdebug v0.44 2012-03-21 10:14:30 -07:00
Paul Kulchenko
92001a4a78 Added option to activate output/console when Run/Debug/Compile commands are executed 2012-03-20 21:25:38 -07:00
Paul Kulchenko
906c70248b Added full screen mode 2012-03-17 21:20:32 -07:00
Paul Kulchenko
4554c67c3e Added killing running process on IDE exit. Allowed killing a running process with Shift-F12. Disabled compilation and debugging when some process is running. 2012-03-10 22:21:36 -08:00
Paul Kulchenko
cb7ee575c7 Fixed an issue with saving a file when no project directory is set 2012-03-10 21:55:31 -08:00
Paul Kulchenko
c758d4c62a Updated MANIFEST with metalua lib 2012-03-09 22:06:18 -08:00
Paul Kulchenko
8c1c06bb16 Fixed missing semicolon in lualibs path; added path for debugger to search under lualibs 2012-03-09 22:01:20 -08:00
Paul Kulchenko
99ca5fe952 Merge branch 'master' of git://estrelaeditor.git.sourceforge.net/gitroot/estrelaeditor/estrelaeditor 2012-03-08 11:49:22 -08:00
Paul Kulchenko
1f064655cc Added metalua library 2012-03-08 11:48:57 -08:00
Paul Kulchenko
9a05cc3678 Fixed missing slash on SaveAs by enforcing trailing slash for the project path 2012-03-08 10:50:47 -08:00
Paul Kulchenko
6da3cb2c32 Disabled buffering of the output for scripts run from IDE 2012-02-27 22:49:00 -08:00
Paul Kulchenko
d2cb7cb1c2 Fixed an issue with a missing path separator, which prevented debugging from executing step commands in some cases 2012-02-22 21:54:36 -08:00
crazybutcher
0ac769ffba bugfix in luxinia1 interpreter 2012-02-16 21:52:35 +01:00
Paul Kulchenko
ef60786e48 Removed debug output when coroutines are edited 2012-02-14 23:35:56 -08:00
Paul Kulchenko
f9c15faab8 Fixed activation of a correct tab when one of the editor tabs is closed 2012-02-13 18:30:45 -08:00
Paul Kulchenko
59b28ef35c Merge branch 'master' of git://estrelaeditor.git.sourceforge.net/gitroot/estrelaeditor/estrelaeditor 2012-02-12 22:42:12 -08:00
Paul Kulchenko
9db7d4ec8a Fixed an issue with file activation from a debugger (files that are 'required' now actiate correctly) 2012-02-12 22:41:41 -08:00
Paul Kulchenko
40eaace714 Disabled dynamic words in the config (zbstudio) 2012-02-10 09:47:05 -08:00
Paul Kulchenko
fe000bb59e Wrapped DragAcceptFiles into a protected call to make it not fail on MacOS (compiled with wxwidgets 2.8.12) 2012-02-09 12:23:47 -08:00
crazybutcher
9aa220df41 Merge branch 'master' of ssh://estrelaeditor.git.sourceforge.net/gitroot/estrelaeditor/estrelaeditor 2012-02-08 20:05:33 +01:00
Paul Kulchenko
97c52f15f3 Added highlight style to markup 2012-02-07 23:41:29 -08:00
Paul Kulchenko
54f578790b Merge branch 'master' of git://estrelaeditor.git.sourceforge.net/gitroot/estrelaeditor/estrelaeditor 2012-02-01 22:29:54 -08:00
Paul Kulchenko
a51a08c1b8 Removed slash conversion in filename on Save; added markup styling on SaveAs 2012-02-01 22:29:28 -08:00
Paul Kulchenko
7620e758a0 Updated error messages to more generic ones 2012-02-01 19:52:55 -08:00
Paul Kulchenko
80da9253dd Normalized the filename path to remove '.' and such 2012-02-01 19:33:08 -08:00
Paul Kulchenko
a133fa900e Fixed the issue of ClosePage method being called with two different parameters 2012-01-31 23:00:55 -08:00
Paul Kulchenko
0fa62e544a Updated markup processing with run and debug commands, http link processing, and opening local files in a new window. Switched from hotspot click event to mouse button processing to avoid the issue with selection being active in a newly loaded page 2012-01-31 22:56:18 -08:00
Paul Kulchenko
601dbef6e5 Added Debug and Run methods to simulate menu commands 2012-01-31 22:49:09 -08:00
unknown
01b3a4fd09 Merge branch 'master' of ssh://estrelaeditor.git.sourceforge.net/gitroot/estrelaeditor/estrelaeditor 2012-01-31 12:08:36 +01:00
crazybutcher
ed0df13acb dx backwards compat mode and replace / in filenames 2012-01-31 12:06:55 +01:00
Paul Kulchenko
642fd63724 Added setting a project folder on initial start (zbstudio) 2012-01-30 18:21:51 -08:00
Paul Kulchenko
a1586cd530 Replaced hardcoded path separater with a method call 2012-01-30 18:19:18 -08:00
Paul Kulchenko
291cba2d79 Merge branch 'master' of git://estrelaeditor.git.sourceforge.net/gitroot/estrelaeditor/estrelaeditor 2012-01-30 15:58:40 -08:00
Paul Kulchenko
96bffc1322 Added markup formatting in the comments 2012-01-30 15:58:28 -08:00
Paul Kulchenko
e88710fbbc Updated debugger to use project dir as basedir if it is specified (rather than the path to the currently edited file) 2012-01-30 15:03:22 -08:00
Paul Kulchenko
99da8be0f8 Fixed the issue of the project dir being returned with two trailing slashes 2012-01-30 15:03:05 -08:00
crazybutcher
c8c654cb2b removed ptx r,g,b,a keywords 2012-01-30 22:05:47 +01:00
crazybutcher
cf68a42547 added nvidia ptx spec 2012-01-30 22:01:57 +01:00
Paul Kulchenko
5c0d4cccdd Added setting the current project directory for the shell to allow 'require' commands to work with local modules 2012-01-29 17:10:55 -08:00
Paul Kulchenko
b979b41688 Fixed an issue with activating the currenly edited file in the file tree 2012-01-29 13:55:59 -08:00
Paul Kulchenko
b807fa9a99 Enforced visibility for shell prompt 2012-01-28 11:59:18 -08:00
Paul Kulchenko
ef5b1b0e09 Removed hardcoded references to the console page/tab 2012-01-28 11:53:56 -08:00
Paul Kulchenko
04a038da45 Moved wrap flags to the line end to make them less noticeable 2012-01-28 11:52:17 -08:00
Paul Kulchenko
bc0e3190d4 Added style processing for font name, font size, visibility and hotspot attributes 2012-01-28 11:20:52 -08:00
Paul Kulchenko
16dcd084e1 Renamed ShellExecuteCode to ...File; added ShellExecuteCode to execute a fragment of code 2012-01-28 10:52:35 -08:00
crazybutcher
15f8c15039 bugfix on close-file 2012-01-25 22:00:28 +01:00
crazybutcher
bff0da36dc minor updates to license (2012) and readme (front-ends explained) 2012-01-21 14:27:03 +01:00
crazybutcher
1e933a6b75 Merge remote-tracking branch 'zbstudio/master' 2012-01-21 14:15:06 +01:00
crazybutcher
c9bb3e01ca added glfw 2.7.2 api and changed luxinia2 to use it instead of glfw3 2012-01-21 14:13:54 +01:00
86 changed files with 18450 additions and 834 deletions

View File

@@ -2,7 +2,7 @@
ZeroBrane Studio sources are released under the MIT License
Copyright (c) 2011 Paul Kulchenko (paul@kulchenko.com)
Copyright (c) 2011-2012 Paul Kulchenko (paul@kulchenko.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -26,9 +26,9 @@ THE SOFTWARE.
Estrela Editor sources are released under the MIT License
Copyright (c) 2008-2011
Copyright (c) 2008-2012
Luxinia DevTeam:
Eike Decker & Christoph Kubisch
Christoph Kubisch & Eike Decker
info at luxinia.de
Permission is hereby granted, free of charge, to any person obtaining a copy

View File

@@ -1,8 +1,10 @@
# Project Description
A simple and extensible Lua IDE and debugger. It supports multiple file
formats, "api" for autocompletion and tooltips, and custom command-line
tools. Its main focus is extensibility for target applications using Lua.
--[[ FEATURES ]]-----------------------------------------------------------
## Features
* Written in Lua, so easily customizable
* Automatically loads several 'plugin' like classes
@@ -23,14 +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)
--[[ INSTALLATION ]]-------------------------------------------------------
## Frontends
git clone git://github.com/pkulchenko/ZeroBraneStudio.git zbstudio
There is currently two front-ends using the same editor engine. The original
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
```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
@@ -38,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 DevTeam: Eike Decker & Christoph Kubisch (info at 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.

930
api/lua/glfw.lua Normal file
View File

@@ -0,0 +1,930 @@
--[[// glfw | GLFW window manager
enum {
/*************************************************************************
* GLFW version
*************************************************************************/
GLFW_VERSION_MAJOR =2,
GLFW_VERSION_MINOR =7,
GLFW_VERSION_REVISION =2,
/*************************************************************************
* Input handling definitions
*************************************************************************/
/* Key and button state/action definitions */
GLFW_RELEASE =0,
GLFW_PRESS =1,
GLFW_TRUE = 1,
GLFW_FALSE = 0,
/* Keyboard key definitions: 8-bit ISO-8859-1 (Latin 1) encoding is used
* for printable keys (such as A-Z, 0-9 etc), and values above 256
* represent special (non-printable) keys (e.g. F1, Page Up etc).
*/
GLFW_KEY_UNKNOWN =-1,
GLFW_KEY_SPACE =32,
GLFW_KEY_APOSTROPHE = 39,
GLFW_KEY_COMMA = 44,
GLFW_KEY_MINUS = 45,
GLFW_KEY_PERIOD = 46,
GLFW_KEY_SLASH = 47,
GLFW_KEY_0 = 48,
GLFW_KEY_1 = 49,
GLFW_KEY_2 = 50,
GLFW_KEY_3 = 51,
GLFW_KEY_4 = 52,
GLFW_KEY_5 = 53,
GLFW_KEY_6 = 54,
GLFW_KEY_7 = 55,
GLFW_KEY_8 = 56,
GLFW_KEY_9 = 57,
GLFW_KEY_SEMICOLON = 59,
GLFW_KEY_EQUAL = 61,
GLFW_KEY_A = 65,
GLFW_KEY_B = 66,
GLFW_KEY_C = 67,
GLFW_KEY_D = 68,
GLFW_KEY_E = 69,
GLFW_KEY_F = 70,
GLFW_KEY_G = 71,
GLFW_KEY_H = 72,
GLFW_KEY_I = 73,
GLFW_KEY_J = 74,
GLFW_KEY_K = 75,
GLFW_KEY_L = 76,
GLFW_KEY_M = 77,
GLFW_KEY_N = 78,
GLFW_KEY_O = 79,
GLFW_KEY_P = 80,
GLFW_KEY_Q = 81,
GLFW_KEY_R = 82,
GLFW_KEY_S = 83,
GLFW_KEY_T = 84,
GLFW_KEY_U = 85,
GLFW_KEY_V = 86,
GLFW_KEY_W = 87,
GLFW_KEY_X = 88,
GLFW_KEY_Y = 89,
GLFW_KEY_Z = 90,
GLFW_KEY_LEFT_BRACKET = 91,
GLFW_KEY_BACKSLASH = 92,
GLFW_KEY_RIGHT_BRACKET = 93,
GLFW_KEY_GRAVE_ACCENT = 96,
GLFW_KEY_WORLD_1 = 161,
GLFW_KEY_WORLD_2 = 162,
GLFW_KEY_SPECIAL =256,
GLFW_KEY_ESC =(GLFW_KEY_SPECIAL+1),
GLFW_KEY_F1 =(GLFW_KEY_SPECIAL+2),
GLFW_KEY_F2 =(GLFW_KEY_SPECIAL+3),
GLFW_KEY_F3 =(GLFW_KEY_SPECIAL+4),
GLFW_KEY_F4 =(GLFW_KEY_SPECIAL+5),
GLFW_KEY_F5 =(GLFW_KEY_SPECIAL+6),
GLFW_KEY_F6 =(GLFW_KEY_SPECIAL+7),
GLFW_KEY_F7 =(GLFW_KEY_SPECIAL+8),
GLFW_KEY_F8 =(GLFW_KEY_SPECIAL+9),
GLFW_KEY_F9 =(GLFW_KEY_SPECIAL+10),
GLFW_KEY_F10 =(GLFW_KEY_SPECIAL+11),
GLFW_KEY_F11 =(GLFW_KEY_SPECIAL+12),
GLFW_KEY_F12 =(GLFW_KEY_SPECIAL+13),
GLFW_KEY_F13 =(GLFW_KEY_SPECIAL+14),
GLFW_KEY_F14 =(GLFW_KEY_SPECIAL+15),
GLFW_KEY_F15 =(GLFW_KEY_SPECIAL+16),
GLFW_KEY_F16 =(GLFW_KEY_SPECIAL+17),
GLFW_KEY_F17 =(GLFW_KEY_SPECIAL+18),
GLFW_KEY_F18 =(GLFW_KEY_SPECIAL+19),
GLFW_KEY_F19 =(GLFW_KEY_SPECIAL+20),
GLFW_KEY_F20 =(GLFW_KEY_SPECIAL+21),
GLFW_KEY_F21 =(GLFW_KEY_SPECIAL+22),
GLFW_KEY_F22 =(GLFW_KEY_SPECIAL+23),
GLFW_KEY_F23 =(GLFW_KEY_SPECIAL+24),
GLFW_KEY_F24 =(GLFW_KEY_SPECIAL+25),
GLFW_KEY_F25 =(GLFW_KEY_SPECIAL+26),
GLFW_KEY_UP =(GLFW_KEY_SPECIAL+27),
GLFW_KEY_DOWN =(GLFW_KEY_SPECIAL+28),
GLFW_KEY_LEFT =(GLFW_KEY_SPECIAL+29),
GLFW_KEY_RIGHT =(GLFW_KEY_SPECIAL+30),
GLFW_KEY_LSHIFT =(GLFW_KEY_SPECIAL+31),
GLFW_KEY_RSHIFT =(GLFW_KEY_SPECIAL+32),
GLFW_KEY_LCTRL =(GLFW_KEY_SPECIAL+33),
GLFW_KEY_RCTRL =(GLFW_KEY_SPECIAL+34),
GLFW_KEY_LALT =(GLFW_KEY_SPECIAL+35),
GLFW_KEY_RALT =(GLFW_KEY_SPECIAL+36),
GLFW_KEY_TAB =(GLFW_KEY_SPECIAL+37),
GLFW_KEY_ENTER =(GLFW_KEY_SPECIAL+38),
GLFW_KEY_BACKSPACE =(GLFW_KEY_SPECIAL+39),
GLFW_KEY_INSERT =(GLFW_KEY_SPECIAL+40),
GLFW_KEY_DEL =(GLFW_KEY_SPECIAL+41),
GLFW_KEY_PAGEUP =(GLFW_KEY_SPECIAL+42),
GLFW_KEY_PAGEDOWN =(GLFW_KEY_SPECIAL+43),
GLFW_KEY_HOME =(GLFW_KEY_SPECIAL+44),
GLFW_KEY_END =(GLFW_KEY_SPECIAL+45),
GLFW_KEY_KP_0 =(GLFW_KEY_SPECIAL+46),
GLFW_KEY_KP_1 =(GLFW_KEY_SPECIAL+47),
GLFW_KEY_KP_2 =(GLFW_KEY_SPECIAL+48),
GLFW_KEY_KP_3 =(GLFW_KEY_SPECIAL+49),
GLFW_KEY_KP_4 =(GLFW_KEY_SPECIAL+50),
GLFW_KEY_KP_5 =(GLFW_KEY_SPECIAL+51),
GLFW_KEY_KP_6 =(GLFW_KEY_SPECIAL+52),
GLFW_KEY_KP_7 =(GLFW_KEY_SPECIAL+53),
GLFW_KEY_KP_8 =(GLFW_KEY_SPECIAL+54),
GLFW_KEY_KP_9 =(GLFW_KEY_SPECIAL+55),
GLFW_KEY_KP_DIVIDE =(GLFW_KEY_SPECIAL+56),
GLFW_KEY_KP_MULTIPLY =(GLFW_KEY_SPECIAL+57),
GLFW_KEY_KP_SUBTRACT =(GLFW_KEY_SPECIAL+58),
GLFW_KEY_KP_ADD =(GLFW_KEY_SPECIAL+59),
GLFW_KEY_KP_DECIMAL =(GLFW_KEY_SPECIAL+60),
GLFW_KEY_KP_EQUAL =(GLFW_KEY_SPECIAL+61),
GLFW_KEY_KP_ENTER =(GLFW_KEY_SPECIAL+62),
GLFW_KEY_KP_NUM_LOCK =(GLFW_KEY_SPECIAL+63),
GLFW_KEY_CAPS_LOCK =(GLFW_KEY_SPECIAL+64),
GLFW_KEY_SCROLL_LOCK =(GLFW_KEY_SPECIAL+65),
GLFW_KEY_PAUSE =(GLFW_KEY_SPECIAL+66),
GLFW_KEY_LSUPER =(GLFW_KEY_SPECIAL+67),
GLFW_KEY_RSUPER =(GLFW_KEY_SPECIAL+68),
GLFW_KEY_MENU =(GLFW_KEY_SPECIAL+69),
GLFW_KEY_LAST =GLFW_KEY_MENU,
/* Mouse button definitions */
GLFW_MOUSE_BUTTON_1 =0,
GLFW_MOUSE_BUTTON_2 =1,
GLFW_MOUSE_BUTTON_3 =2,
GLFW_MOUSE_BUTTON_4 =3,
GLFW_MOUSE_BUTTON_5 =4,
GLFW_MOUSE_BUTTON_6 =5,
GLFW_MOUSE_BUTTON_7 =6,
GLFW_MOUSE_BUTTON_8 =7,
GLFW_MOUSE_BUTTON_LAST =GLFW_MOUSE_BUTTON_8,
/* Mouse button aliases */
GLFW_MOUSE_BUTTON_LEFT =GLFW_MOUSE_BUTTON_1,
GLFW_MOUSE_BUTTON_RIGHT =GLFW_MOUSE_BUTTON_2,
GLFW_MOUSE_BUTTON_MIDDLE =GLFW_MOUSE_BUTTON_3,
/* Joystick identifiers */
GLFW_JOYSTICK_1 =0,
GLFW_JOYSTICK_2 =1,
GLFW_JOYSTICK_3 =2,
GLFW_JOYSTICK_4 =3,
GLFW_JOYSTICK_5 =4,
GLFW_JOYSTICK_6 =5,
GLFW_JOYSTICK_7 =6,
GLFW_JOYSTICK_8 =7,
GLFW_JOYSTICK_9 =8,
GLFW_JOYSTICK_10 =9,
GLFW_JOYSTICK_11 =10,
GLFW_JOYSTICK_12 =11,
GLFW_JOYSTICK_13 =12,
GLFW_JOYSTICK_14 =13,
GLFW_JOYSTICK_15 =14,
GLFW_JOYSTICK_16 =15,
GLFW_JOYSTICK_LAST =GLFW_JOYSTICK_16,
/*************************************************************************
* Other definitions
*************************************************************************/
/* glfwOpenWindow modes */
GLFW_WINDOW =0x00010001,
GLFW_FULLSCREEN =0x00010002,
/* glfwGetWindowParam tokens */
GLFW_OPENED =0x00020001,
GLFW_ACTIVE =0x00020002,
GLFW_ICONIFIED =0x00020003,
GLFW_ACCELERATED =0x00020004,
GLFW_RED_BITS =0x00020005,
GLFW_GREEN_BITS =0x00020006,
GLFW_BLUE_BITS =0x00020007,
GLFW_ALPHA_BITS =0x00020008,
GLFW_DEPTH_BITS =0x00020009,
GLFW_STENCIL_BITS =0x0002000A,
/* The following constants are used for both glfwGetWindowParam
* and glfwOpenWindowHint
*/
GLFW_REFRESH_RATE =0x0002000B,
GLFW_ACCUM_RED_BITS =0x0002000C,
GLFW_ACCUM_GREEN_BITS =0x0002000D,
GLFW_ACCUM_BLUE_BITS =0x0002000E,
GLFW_ACCUM_ALPHA_BITS =0x0002000F,
GLFW_AUX_BUFFERS =0x00020010,
GLFW_STEREO =0x00020011,
GLFW_WINDOW_NO_RESIZE =0x00020012,
GLFW_FSAA_SAMPLES =0x00020013,
GLFW_OPENGL_VERSION_MAJOR =0x00020014,
GLFW_OPENGL_VERSION_MINOR =0x00020015,
GLFW_OPENGL_FORWARD_COMPAT =0x00020016,
GLFW_OPENGL_DEBUG_CONTEXT =0x00020017,
GLFW_OPENGL_PROFILE =0x00020018,
/* GLFW_OPENGL_PROFILE tokens */
GLFW_OPENGL_CORE_PROFILE =0x00050001,
GLFW_OPENGL_COMPAT_PROFILE =0x00050002,
/* glfwEnable/glfwDisable tokens */
GLFW_MOUSE_CURSOR =0x00030001,
GLFW_STICKY_KEYS =0x00030002,
GLFW_STICKY_MOUSE_BUTTONS =0x00030003,
GLFW_SYSTEM_KEYS =0x00030004,
GLFW_KEY_REPEAT =0x00030005,
GLFW_AUTO_POLL_EVENTS =0x00030006,
/* glfwWaitThread wait modes */
GLFW_WAIT =0x00040001,
GLFW_NOWAIT =0x00040002,
/* glfwGetJoystickParam tokens */
GLFW_PRESENT =0x00050001,
GLFW_AXES =0x00050002,
GLFW_BUTTONS =0x00050003,
/* glfwReadImage/glfwLoadTexture2D flags */
GLFW_NO_RESCALE_BIT =0x00000001 /* Only for glfwReadImage */,
GLFW_ORIGIN_UL_BIT =0x00000002,
GLFW_BUILD_MIPMAPS_BIT =0x00000004 /* Only for glfwLoadTexture2D */,
GLFW_ALPHA_MAP_BIT =0x00000008,
/* Time spans longer than this (seconds) are considered to be infinity */
};
const float GLFW_INFINITY =100000.0;
/* The video mode structure used by glfwGetVideoModes() */
typedef struct {
int Width, Height;
int RedBits, BlueBits, GreenBits;
} GLFWvidmode;
/* Image/texture information */
typedef struct {
int Width, Height;
int Format;
int BytesPerPixel;
unsigned char *Data;
} GLFWimage;
/* Thread ID */
typedef int GLFWthread;
/* Mutex object */
typedef void * GLFWmutex;
/* Condition variable object */
typedef void * GLFWcond;
/* Function pointer types */
typedef void (GLFWCALL * GLFWwindowsizefun)(int,int);
typedef int (GLFWCALL * GLFWwindowclosefun)(void);
typedef void (GLFWCALL * GLFWwindowrefreshfun)(void);
typedef void (GLFWCALL * GLFWmousebuttonfun)(int,int);
typedef void (GLFWCALL * GLFWmouseposfun)(int,int);
typedef void (GLFWCALL * GLFWmousewheelfun)(int);
typedef void (GLFWCALL * GLFWkeyfun)(int,int);
typedef void (GLFWCALL * GLFWcharfun)(int,int);
typedef void (GLFWCALL * GLFWthreadfun)(void *);
/*************************************************************************
* Prototypes
*************************************************************************/
/* GLFW initialization, termination and version querying */
int glfwInit( void );
void glfwTerminate( void );
void glfwGetVersion( int *major, int *minor, int *rev );
/* Window handling */
int glfwOpenWindow( int width, int height, int redbits, int greenbits, int bluebits, int alphabits, int depthbits, int stencilbits, int mode );
void glfwOpenWindowHint( int target, int hint );
void glfwCloseWindow( void );
void glfwSetWindowTitle( const char *title );
void glfwGetWindowSize( int *width, int *height );
void glfwSetWindowSize( int width, int height );
void glfwSetWindowPos( int x, int y );
void glfwIconifyWindow( void );
void glfwRestoreWindow( void );
void glfwSwapBuffers( void );
void glfwSwapInterval( int interval );
int glfwGetWindowParam( int param );
void glfwSetWindowSizeCallback( GLFWwindowsizefun cbfun );
void glfwSetWindowCloseCallback( GLFWwindowclosefun cbfun );
void glfwSetWindowRefreshCallback( GLFWwindowrefreshfun cbfun );
/* Video mode functions */
int glfwGetVideoModes( GLFWvidmode *list, int maxcount );
void glfwGetDesktopMode( GLFWvidmode *mode );
/* Input handling */
void glfwPollEvents( void );
void glfwWaitEvents( void );
int glfwGetKey( int key );
int glfwGetMouseButton( int button );
void glfwGetMousePos( int *xpos, int *ypos );
void glfwSetMousePos( int xpos, int ypos );
int glfwGetMouseWheel( void );
void glfwSetMouseWheel( int pos );
void glfwSetKeyCallback( GLFWkeyfun cbfun );
void glfwSetCharCallback( GLFWcharfun cbfun );
void glfwSetMouseButtonCallback( GLFWmousebuttonfun cbfun );
void glfwSetMousePosCallback( GLFWmouseposfun cbfun );
void glfwSetMouseWheelCallback( GLFWmousewheelfun cbfun );
/* Joystick input */
int glfwGetJoystickParam( int joy, int param );
int glfwGetJoystickPos( int joy, float *pos, int numaxes );
int glfwGetJoystickButtons( int joy, unsigned char *buttons, int numbuttons );
/* Time */
double glfwGetTime( void );
void glfwSetTime( double time );
void glfwSleep( double time );
/* Extension support */
int glfwExtensionSupported( const char *extension );
void* glfwGetProcAddress( const char *procname );
void glfwGetGLVersion( int *major, int *minor, int *rev );
/* Threading support */
GLFWthread glfwCreateThread( GLFWthreadfun fun, void *arg );
void glfwDestroyThread( GLFWthread ID );
int glfwWaitThread( GLFWthread ID, int waitmode );
GLFWthread glfwGetThreadID( void );
GLFWmutex glfwCreateMutex( void );
void glfwDestroyMutex( GLFWmutex mutex );
void glfwLockMutex( GLFWmutex mutex );
void glfwUnlockMutex( GLFWmutex mutex );
GLFWcond glfwCreateCond( void );
void glfwDestroyCond( GLFWcond cond );
void glfwWaitCond( GLFWcond cond, GLFWmutex mutex, double timeout );
void glfwSignalCond( GLFWcond cond );
void glfwBroadcastCond( GLFWcond cond );
int glfwGetNumberOfProcessors( void );
/* Enable/disable functions */
void glfwEnable( int token );
void glfwDisable( int token );
/* Image/texture I/O support */
int glfwReadImage( const char *name, GLFWimage *img, int flags );
int glfwReadMemoryImage( const void *data, long size, GLFWimage *img, int flags );
void glfwFreeImage( GLFWimage *img );
int glfwLoadTexture2D( const char *name, int flags );
int glfwLoadMemoryTexture2D( const void *data, long size, int flags );
int glfwLoadTextureImage2D( GLFWimage *img, int flags );
]]
--auto-generated api from ffi headers
local api =
{
["GLFW_VERSION_MAJOR"] = { type ='value', },
["GLFW_VERSION_MINOR"] = { type ='value', },
["GLFW_VERSION_REVISION"] = { type ='value', },
["GLFW_RELEASE"] = { type ='value', },
["GLFW_PRESS"] = { type ='value', },
["GLFW_KEY_UNKNOWN"] = { type ='value', },
["GLFW_KEY_SPACE"] = { type ='value', },
["GLFW_KEY_APOSTROPHE"] = { type ='value', },
["GLFW_KEY_COMMA"] = { type ='value', },
["GLFW_KEY_MINUS"] = { type ='value', },
["GLFW_KEY_PERIOD"] = { type ='value', },
["GLFW_KEY_SLASH"] = { type ='value', },
["GLFW_KEY_0"] = { type ='value', },
["GLFW_KEY_1"] = { type ='value', },
["GLFW_KEY_2"] = { type ='value', },
["GLFW_KEY_3"] = { type ='value', },
["GLFW_KEY_4"] = { type ='value', },
["GLFW_KEY_5"] = { type ='value', },
["GLFW_KEY_6"] = { type ='value', },
["GLFW_KEY_7"] = { type ='value', },
["GLFW_KEY_8"] = { type ='value', },
["GLFW_KEY_9"] = { type ='value', },
["GLFW_KEY_SEMICOLON"] = { type ='value', },
["GLFW_KEY_EQUAL"] = { type ='value', },
["GLFW_KEY_A"] = { type ='value', },
["GLFW_KEY_B"] = { type ='value', },
["GLFW_KEY_C"] = { type ='value', },
["GLFW_KEY_D"] = { type ='value', },
["GLFW_KEY_E"] = { type ='value', },
["GLFW_KEY_F"] = { type ='value', },
["GLFW_KEY_G"] = { type ='value', },
["GLFW_KEY_H"] = { type ='value', },
["GLFW_KEY_I"] = { type ='value', },
["GLFW_KEY_J"] = { type ='value', },
["GLFW_KEY_K"] = { type ='value', },
["GLFW_KEY_L"] = { type ='value', },
["GLFW_KEY_M"] = { type ='value', },
["GLFW_KEY_N"] = { type ='value', },
["GLFW_KEY_O"] = { type ='value', },
["GLFW_KEY_P"] = { type ='value', },
["GLFW_KEY_Q"] = { type ='value', },
["GLFW_KEY_R"] = { type ='value', },
["GLFW_KEY_S"] = { type ='value', },
["GLFW_KEY_T"] = { type ='value', },
["GLFW_KEY_U"] = { type ='value', },
["GLFW_KEY_V"] = { type ='value', },
["GLFW_KEY_W"] = { type ='value', },
["GLFW_KEY_X"] = { type ='value', },
["GLFW_KEY_Y"] = { type ='value', },
["GLFW_KEY_Z"] = { type ='value', },
["GLFW_KEY_LEFT_BRACKET"] = { type ='value', },
["GLFW_KEY_BACKSLASH"] = { type ='value', },
["GLFW_KEY_RIGHT_BRACKET"] = { type ='value', },
["GLFW_KEY_GRAVE_ACCENT"] = { type ='value', },
["GLFW_KEY_WORLD_1"] = { type ='value', },
["GLFW_KEY_WORLD_2"] = { type ='value', },
["GLFW_KEY_SPECIAL"] = { type ='value', },
["GLFW_KEY_ESC"] = { type ='value', },
["GLFW_KEY_F1"] = { type ='value', },
["GLFW_KEY_F2"] = { type ='value', },
["GLFW_KEY_F3"] = { type ='value', },
["GLFW_KEY_F4"] = { type ='value', },
["GLFW_KEY_F5"] = { type ='value', },
["GLFW_KEY_F6"] = { type ='value', },
["GLFW_KEY_F7"] = { type ='value', },
["GLFW_KEY_F8"] = { type ='value', },
["GLFW_KEY_F9"] = { type ='value', },
["GLFW_KEY_F10"] = { type ='value', },
["GLFW_KEY_F11"] = { type ='value', },
["GLFW_KEY_F12"] = { type ='value', },
["GLFW_KEY_F13"] = { type ='value', },
["GLFW_KEY_F14"] = { type ='value', },
["GLFW_KEY_F15"] = { type ='value', },
["GLFW_KEY_F16"] = { type ='value', },
["GLFW_KEY_F17"] = { type ='value', },
["GLFW_KEY_F18"] = { type ='value', },
["GLFW_KEY_F19"] = { type ='value', },
["GLFW_KEY_F20"] = { type ='value', },
["GLFW_KEY_F21"] = { type ='value', },
["GLFW_KEY_F22"] = { type ='value', },
["GLFW_KEY_F23"] = { type ='value', },
["GLFW_KEY_F24"] = { type ='value', },
["GLFW_KEY_F25"] = { type ='value', },
["GLFW_KEY_UP"] = { type ='value', },
["GLFW_KEY_DOWN"] = { type ='value', },
["GLFW_KEY_LEFT"] = { type ='value', },
["GLFW_KEY_RIGHT"] = { type ='value', },
["GLFW_KEY_LSHIFT"] = { type ='value', },
["GLFW_KEY_RSHIFT"] = { type ='value', },
["GLFW_KEY_LCTRL"] = { type ='value', },
["GLFW_KEY_RCTRL"] = { type ='value', },
["GLFW_KEY_LALT"] = { type ='value', },
["GLFW_KEY_RALT"] = { type ='value', },
["GLFW_KEY_TAB"] = { type ='value', },
["GLFW_KEY_ENTER"] = { type ='value', },
["GLFW_KEY_BACKSPACE"] = { type ='value', },
["GLFW_KEY_INSERT"] = { type ='value', },
["GLFW_KEY_DEL"] = { type ='value', },
["GLFW_KEY_PAGEUP"] = { type ='value', },
["GLFW_KEY_PAGEDOWN"] = { type ='value', },
["GLFW_KEY_HOME"] = { type ='value', },
["GLFW_KEY_END"] = { type ='value', },
["GLFW_KEY_KP_0"] = { type ='value', },
["GLFW_KEY_KP_1"] = { type ='value', },
["GLFW_KEY_KP_2"] = { type ='value', },
["GLFW_KEY_KP_3"] = { type ='value', },
["GLFW_KEY_KP_4"] = { type ='value', },
["GLFW_KEY_KP_5"] = { type ='value', },
["GLFW_KEY_KP_6"] = { type ='value', },
["GLFW_KEY_KP_7"] = { type ='value', },
["GLFW_KEY_KP_8"] = { type ='value', },
["GLFW_KEY_KP_9"] = { type ='value', },
["GLFW_KEY_KP_DIVIDE"] = { type ='value', },
["GLFW_KEY_KP_MULTIPLY"] = { type ='value', },
["GLFW_KEY_KP_SUBTRACT"] = { type ='value', },
["GLFW_KEY_KP_ADD"] = { type ='value', },
["GLFW_KEY_KP_DECIMAL"] = { type ='value', },
["GLFW_KEY_KP_EQUAL"] = { type ='value', },
["GLFW_KEY_KP_ENTER"] = { type ='value', },
["GLFW_KEY_KP_NUM_LOCK"] = { type ='value', },
["GLFW_KEY_CAPS_LOCK"] = { type ='value', },
["GLFW_KEY_SCROLL_LOCK"] = { type ='value', },
["GLFW_KEY_PAUSE"] = { type ='value', },
["GLFW_KEY_LSUPER"] = { type ='value', },
["GLFW_KEY_RSUPER"] = { type ='value', },
["GLFW_KEY_MENU"] = { type ='value', },
["GLFW_KEY_LAST"] = { type ='value', },
["GLFW_MOUSE_BUTTON_1"] = { type ='value', },
["GLFW_MOUSE_BUTTON_2"] = { type ='value', },
["GLFW_MOUSE_BUTTON_3"] = { type ='value', },
["GLFW_MOUSE_BUTTON_4"] = { type ='value', },
["GLFW_MOUSE_BUTTON_5"] = { type ='value', },
["GLFW_MOUSE_BUTTON_6"] = { type ='value', },
["GLFW_MOUSE_BUTTON_7"] = { type ='value', },
["GLFW_MOUSE_BUTTON_8"] = { type ='value', },
["GLFW_MOUSE_BUTTON_LAST"] = { type ='value', },
["GLFW_MOUSE_BUTTON_LEFT"] = { type ='value', },
["GLFW_MOUSE_BUTTON_RIGHT"] = { type ='value', },
["GLFW_MOUSE_BUTTON_MIDDLE"] = { type ='value', },
["GLFW_JOYSTICK_1"] = { type ='value', },
["GLFW_JOYSTICK_2"] = { type ='value', },
["GLFW_JOYSTICK_3"] = { type ='value', },
["GLFW_JOYSTICK_4"] = { type ='value', },
["GLFW_JOYSTICK_5"] = { type ='value', },
["GLFW_JOYSTICK_6"] = { type ='value', },
["GLFW_JOYSTICK_7"] = { type ='value', },
["GLFW_JOYSTICK_8"] = { type ='value', },
["GLFW_JOYSTICK_9"] = { type ='value', },
["GLFW_JOYSTICK_10"] = { type ='value', },
["GLFW_JOYSTICK_11"] = { type ='value', },
["GLFW_JOYSTICK_12"] = { type ='value', },
["GLFW_JOYSTICK_13"] = { type ='value', },
["GLFW_JOYSTICK_14"] = { type ='value', },
["GLFW_JOYSTICK_15"] = { type ='value', },
["GLFW_JOYSTICK_16"] = { type ='value', },
["GLFW_JOYSTICK_LAST"] = { type ='value', },
["GLFW_WINDOW"] = { type ='value', },
["GLFW_FULLSCREEN"] = { type ='value', },
["GLFW_OPENED"] = { type ='value', },
["GLFW_ACTIVE"] = { type ='value', },
["GLFW_ICONIFIED"] = { type ='value', },
["GLFW_ACCELERATED"] = { type ='value', },
["GLFW_RED_BITS"] = { type ='value', },
["GLFW_GREEN_BITS"] = { type ='value', },
["GLFW_BLUE_BITS"] = { type ='value', },
["GLFW_ALPHA_BITS"] = { type ='value', },
["GLFW_DEPTH_BITS"] = { type ='value', },
["GLFW_STENCIL_BITS"] = { type ='value', },
["GLFW_REFRESH_RATE"] = { type ='value', },
["GLFW_ACCUM_RED_BITS"] = { type ='value', },
["GLFW_ACCUM_GREEN_BITS"] = { type ='value', },
["GLFW_ACCUM_BLUE_BITS"] = { type ='value', },
["GLFW_ACCUM_ALPHA_BITS"] = { type ='value', },
["GLFW_AUX_BUFFERS"] = { type ='value', },
["GLFW_STEREO"] = { type ='value', },
["GLFW_WINDOW_NO_RESIZE"] = { type ='value', },
["GLFW_FSAA_SAMPLES"] = { type ='value', },
["GLFW_OPENGL_VERSION_MAJOR"] = { type ='value', },
["GLFW_OPENGL_VERSION_MINOR"] = { type ='value', },
["GLFW_OPENGL_FORWARD_COMPAT"] = { type ='value', },
["GLFW_OPENGL_DEBUG_CONTEXT"] = { type ='value', },
["GLFW_OPENGL_PROFILE"] = { type ='value', },
["GLFW_OPENGL_CORE_PROFILE"] = { type ='value', },
["GLFW_OPENGL_COMPAT_PROFILE"] = { type ='value', },
["GLFW_MOUSE_CURSOR"] = { type ='value', },
["GLFW_STICKY_KEYS"] = { type ='value', },
["GLFW_STICKY_MOUSE_BUTTONS"] = { type ='value', },
["GLFW_SYSTEM_KEYS"] = { type ='value', },
["GLFW_KEY_REPEAT"] = { type ='value', },
["GLFW_AUTO_POLL_EVENTS"] = { type ='value', },
["GLFW_WAIT"] = { type ='value', },
["GLFW_NOWAIT"] = { type ='value', },
["GLFW_PRESENT"] = { type ='value', },
["GLFW_AXES"] = { type ='value', },
["GLFW_BUTTONS"] = { type ='value', },
["GLFW_NO_RESCALE_BIT"] = { type ='value', },
["GLFW_ORIGIN_UL_BIT"] = { type ='value', },
["GLFW_BUILD_MIPMAPS_BIT"] = { type ='value', },
["GLFW_ALPHA_MAP_BIT"] = { type ='value', },
["glfwInit"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(void)", },
["glfwTerminate"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(void)", },
["glfwGetVersion"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(int *major, int *minor, int *rev)", },
["glfwOpenWindow"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(int width, int height, int redbits, int greenbits, int bluebits, int alphabits, int depthbits, int stencilbits, int mode)", },
["glfwOpenWindowHint"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(int target, int hint)", },
["glfwCloseWindow"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(void)", },
["glfwSetWindowTitle"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(const char *title)", },
["glfwGetWindowSize"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(int *width, int *height)", },
["glfwSetWindowSize"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(int width, int height)", },
["glfwSetWindowPos"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(int x, int y)", },
["glfwIconifyWindow"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(void)", },
["glfwRestoreWindow"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(void)", },
["glfwSwapBuffers"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(void)", },
["glfwSwapInterval"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(int interval)", },
["glfwGetWindowParam"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(int param)", },
["glfwSetWindowSizeCallback"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWwindowsizefun cbfun)", },
["glfwSetWindowCloseCallback"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWwindowclosefun cbfun)", },
["glfwSetWindowRefreshCallback"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWwindowrefreshfun cbfun)", },
["glfwGetVideoModes"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(GLFWvidmode *list, int maxcount)", },
["glfwGetDesktopMode"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWvidmode *mode)", },
["glfwPollEvents"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(void)", },
["glfwWaitEvents"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(void)", },
["glfwGetKey"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(int key)", },
["glfwGetMouseButton"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(int button)", },
["glfwGetMousePos"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(int *xpos, int *ypos)", },
["glfwSetMousePos"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(int xpos, int ypos)", },
["glfwGetMouseWheel"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(void)", },
["glfwSetMouseWheel"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(int pos)", },
["glfwSetKeyCallback"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWkeyfun cbfun)", },
["glfwSetCharCallback"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWcharfun cbfun)", },
["glfwSetMouseButtonCallback"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWmousebuttonfun cbfun)", },
["glfwSetMousePosCallback"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWmouseposfun cbfun)", },
["glfwSetMouseWheelCallback"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWmousewheelfun cbfun)", },
["glfwGetJoystickParam"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(int joy, int param)", },
["glfwGetJoystickPos"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(int joy, float *pos, int numaxes)", },
["glfwGetJoystickButtons"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(int joy, unsigned char *buttons, int numbuttons)", },
["glfwGetTime"] = { type ='function',
description = "",
returns = "(double)",
valuetype = nil,
args = "(void)", },
["glfwSetTime"] = { type ='function',
description = "",
returns = "(void)",
valuetype = nil,
args = "(double time)", },
["glfwSleep"] = { type ='function',
description = "",
returns = "(void)",
valuetype = nil,
args = "(double time)", },
["glfwExtensionSupported"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(const char *extension)", },
["glfwGetProcAddress"] = { type ='function',
description = "",
returns = "(void*)",
valuetype = nil,
args = "(const char *procname)", },
["glfwGetGLVersion"] = { type ='function',
description = "",
returns = "(void)",
valuetype = nil,
args = "(int *major, int *minor, int *rev)", },
["glfwCreateThread"] = { type ='function',
description = "",
returns = "(GLFWthread)",
valuetype = nil,
args = "(GLFWthreadfun fun, void *arg)", },
["glfwDestroyThread"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWthread ID)", },
["glfwWaitThread"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(GLFWthread ID, int waitmode)", },
["glfwGetThreadID"] = { type ='function',
description = "",
returns = "(GLFWthread)",
valuetype = nil,
args = "(void)", },
["glfwCreateMutex"] = { type ='function',
description = "",
returns = "(GLFWmutex)",
valuetype = nil,
args = "(void)", },
["glfwDestroyMutex"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWmutex mutex)", },
["glfwLockMutex"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWmutex mutex)", },
["glfwUnlockMutex"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWmutex mutex)", },
["glfwCreateCond"] = { type ='function',
description = "",
returns = "(GLFWcond)",
valuetype = nil,
args = "(void)", },
["glfwDestroyCond"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWcond cond)", },
["glfwWaitCond"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWcond cond, GLFWmutex mutex, double timeout)", },
["glfwSignalCond"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWcond cond)", },
["glfwBroadcastCond"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWcond cond)", },
["glfwGetNumberOfProcessors"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(void)", },
["glfwEnable"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(int token)", },
["glfwDisable"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(int token)", },
["glfwReadImage"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(const char *name, GLFWimage *img, int flags)", },
["glfwReadMemoryImage"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(const void *data, long size, GLFWimage *img, int flags)", },
["glfwFreeImage"] = { type ='function',
description = "",
returns = "()",
valuetype = nil,
args = "(GLFWimage *img)", },
["glfwLoadTexture2D"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(const char *name, int flags)", },
["glfwLoadMemoryTexture2D"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(const void *data, long size, int flags)", },
["glfwLoadTextureImage2D"] = { type ='function',
description = "",
returns = "(int)",
valuetype = nil,
args = "(GLFWimage *img, int flags)", },
["GLFWvidmode"] = { type ='class',
description = "",
childs = {
["Width"] = { type ='value', description = "int", valuetype = nil, },
["Height"] = { type ='value', description = "int", valuetype = nil, },
["RedBits"] = { type ='value', description = "int", valuetype = nil, },
["BlueBits"] = { type ='value', description = "int", valuetype = nil, },
["GreenBits"] = { type ='value', description = "int", valuetype = nil, },
}
},
["GLFWimage"] = { type ='class',
description = "",
childs = {
["Width"] = { type ='value', description = "int", valuetype = nil, },
["Height"] = { type ='value', description = "int", valuetype = nil, },
["Format"] = { type ='value', description = "int", valuetype = nil, },
["BytesPerPixel"] = { type ='value', description = "int", valuetype = nil, },
}
},
}
return {
glfw = {
type = 'lib',
description = "GLFW window manager",
childs = api,
},
}

4301
api/lua/love2d.lua Normal file

File diff suppressed because it is too large Load Diff

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

Binary file not shown.

BIN
bin/clibs/ssl.dll Normal file

Binary file not shown.

27
cfg/user-sample.lua Normal file
View File

@@ -0,0 +1,27 @@
--[[-- Rename this file to `user.lua` to get loaded
Configuration files are loaded in the following order
1. <application>\config.lua
2. cfg\user.lua
3. -cfg commandline strings
-- an example of how loaded configuration can be modified from this file
local G = ... -- this now points to the global environment in the script
local luaspec = G.ide.specs['lua']
luaspec.exts[2] = "luaz"
luaspec.keywords[1] = luaspec.keywords[1] .. ' foo'
-- these changes are going to be mapped to ide.config.editor...
-- change encoding to Cyrillic
editor.fontencoding = G.wx.wxFONTENCODING_ISO8859_5
-- or WinCyrillic
editor.fontencoding = G.wx.wxFONTENCODING_CP1251
outputshell.fontencoding = G.wx.wxFONTENCODING_CP1251
-- specify full path to love2d executable; this is only needed
-- if the game folder and the executable are NOT in the same folder.
path.love2d = 'd:/lua/love/love' -- set the path of love executable
--]]--

View File

@@ -1,9 +0,0 @@
--[[--
estrela loads configs in the following order
1. <application>\config.lua
2. cfg\user.lua
3. -cfg commandline strings
--]]--

View File

@@ -7,7 +7,7 @@ return {
local bottomnotebook = ide.frame.bottomnotebook
bottomnotebook:SetSelection(1)
ShellExecuteCode(wfilename)
ShellExecuteFile(wfilename)
end,
fprojdir = function(self,wfilename)
return wfilename:GetPath(wx.wxPATH_GET_VOLUME)

28
interpreters/love2d.lua Normal file
View File

@@ -0,0 +1,28 @@
return {
name = "Love2d",
description = "Love2d game engine",
api = {"baselib", "love2d"},
frun = function(self,wfilename,rundebug)
if rundebug then DebuggerAttachDefault() end
local love2d = ide.config.path.love2d
or wx.wxFileName(self:fprojdir(wfilename)):GetPath(wx.wxPATH_GET_VOLUME)
.. '/love'
local cmd = ('"%s" "%s"%s'):format(string.gsub(love2d, "\\","/"),
self:fprojdir(wfilename), rundebug and ' -debug' or '')
-- CommandLineRun(cmd,wdir,tooutput,nohide,stringcallback,uid,endcallback)
return CommandLineRun(cmd,self:fworkdir(wfilename),true,false,nil,nil,
function() ide.debugger.pid = nil end)
end,
fprojdir = function(self,wfilename)
return wfilename:GetPath(wx.wxPATH_GET_VOLUME)
end,
fworkdir = function (self,wfilename)
return ide.config.path.projectdir
or wfilename:GetPath(wx.wxPATH_GET_VOLUME)
end,
hasdebugger = true,
fattachdebug = function(self)
DebuggerAttachDefault()
end,
scratchextloop = true,
}

View File

@@ -1,22 +0,0 @@
return {
name = "Lua",
description = "Commandline Lua interpreter",
api = {"wxwidgets","baselib"},
frun = function(self,wfilename)
local mainpath = ide.editorFilename:gsub("[^/\\]+$","")
local filepath = wfilename:GetFullPath()
local code = ([[
xpcall(function() dofile '%s' end,
function(err) print(debug.traceback(err)) end)
]]):format(filepath:gsub("\\","/"))
local cmd = '"'..mainpath..'/bin/lua.exe" -e "'..code..'"'
CommandLineRun(cmd,self:fworkdir(wfilename),true,false)
end,
fprojdir = function(self,wfilename)
return wfilename:GetPath(wx.wxPATH_GET_VOLUME)
end,
fworkdir = function (self,wfilename)
return ide.config.path.projectdir and ide.config.path.projectdir:len()>0 and
ide.config.path.projectdir
end,
}

View File

@@ -1,23 +1,36 @@
local mainpath = string.gsub(ide.editorFilename:gsub("[^/\\]+$",""),"\\","/")
local os = ide.osname
local clibs =
os == "Windows" and mainpath.."bin/?.dll;"..mainpath.."bin/clibs/?.dll" or
os == "Macintosh" and mainpath.."bin/lib?.dylib;"..mainpath.."bin/clibs/?.dylib" or
os == "Unix" and mainpath.."bin/?.so;"..mainpath.."bin/clibs/?.so" or nil
wx.wxSetEnv("LUA_PATH", package.path .. ';'
.. mainpath.."lualibs/?/?.lua;"..mainpath.."lualibs/?.lua")
if clibs then wx.wxSetEnv("LUA_CPATH", package.cpath .. ';' .. clibs) end
local macExe = mainpath..'bin/lua.app/Contents/MacOS/lua'
local exe = (os == "Macintosh" and wx.wxFileExists(macExe)
and macExe or mainpath..'bin/lua')
return {
name = "Lua Debug",
description = "Commandline Lua interpreter",
name = "Lua with Debugger",
description = "Lua interpreter with debugger",
api = {"wxwidgets","baselib"},
frun = function(self,wfilename,rundebug)
local mainpath = string.gsub(ide.editorFilename:gsub("[^/\\]+$",""),"\\","/")
local filepath = string.gsub(wfilename:GetFullPath(), "\\","/")
local script
if rundebug then
DebuggerAttachDefault()
script = (""..
"package.path=package.path..';"..mainpath.."lualibs/?/?.lua';"..
"package.cpath=package.cpath..';"..mainpath.."bin/clibs/?.dll';"..
"require 'mobdebug'; io.stdout:setvbuf('no'); mobdebug.loop('" .. wx.wxGetHostName().."',"..ide.debugger.portnumber..")")
script = rundebug
else
script = ([[dofile '%s']]):format(filepath)
end
local code = ([[xpcall(function() %s end,function(err) print(debug.traceback(err)) end)]]):format(script)
local cmd = '"'..mainpath..'bin/lua.exe" -e "'..code..'"'
return CommandLineRun(cmd,self:fworkdir(wfilename),true,false)
local code = ([[xpcall(function() io.stdout:setvbuf('no'); %s end,function(err) print(debug.traceback(err)) end)]]):format(script)
local cmd = '"'..exe..'" -e "'..code..'"'
-- CommandLineRun(cmd,wdir,tooutput,nohide,stringcallback,uid,endcallback)
return CommandLineRun(cmd,self:fworkdir(wfilename),true,false,nil,nil,
function() ide.debugger.pid = nil end)
end,
fprojdir = function(self,wfilename)
return wfilename:GetPath(wx.wxPATH_GET_VOLUME)

View File

@@ -7,7 +7,7 @@ return {
name = "Luxinia",
description = "Luxinia project",
api = {"luxiniaapi","baselib"},
fcmdline = function(self,wfilename)
frun = function(self,wfilename,withdebug)
local projdir = ide.config.path.projectdir
local endstr = (projdir and projdir:len()>0
and " -p "..projdir or "")
@@ -18,6 +18,7 @@ return {
local cmd = 'luxinia.exe --nologo'..endstr
CommandLineRun(cmd,ide.config.path.luxinia,true,true)
end,
fuid = function(self,wfilename) return "luxinia "..(ide.config.path.projectdir or "") end,
fprojdir = function(self,wfilename)
local path = GetPathWithSep(wfilename)
fname = wx.wxFileName(path)

View File

@@ -6,7 +6,7 @@ end
return {
name = "Luxinia2",
description = "Luxinia2",
api = {"baselib","cg30","cggl30","glfw3","glewgl","assimp20","luxmath","luxgfx","luxscene","luajit2",},
api = {"baselib","cg30","cggl30","glfw","glewgl","assimp20","luxmath","luxgfx","luxscene","luajit2",},
finitclient = function(self)
if (not CommandLineRunning(self:fuid(wfilename))) then return end
@@ -52,7 +52,7 @@ return {
local editorDir = string.gsub(ide.editorFilename:gsub("[^/\\]+$",""),"\\","/")
script = ""..
"package.path=package.path..';"..editorDir.."lualibs/?/?.lua';"..
"require 'mobdebug'; io.stdout:setvbuf('no'); mobdebug.start('" .. wx.wxGetHostName().."',"..ide.debugger.portnumber..")"
"io.stdout:setvbuf('no'); require('mobdebug').start('" .. ide.debugger.hostname.."',"..ide.debugger.portnumber..")"
args = args..' -es "'..script..'"'..startargs
else

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

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

View File

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

View File

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

View File

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

1431
lualibs/luainspect/init.lua Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

1271
lualibs/metalua/compile.lua Normal file

File diff suppressed because it is too large Load Diff

756
lualibs/metalua/gg.lua Normal file
View File

@@ -0,0 +1,756 @@
----------------------------------------------------------------------
-- Metalua.
--
-- Summary: parser generator. Collection of higher order functors,
-- which allow to build and combine parsers. Relies on a lexer
-- that supports the same API as the one exposed in mll.lua.
--
----------------------------------------------------------------------
--
-- Copyright (c) 2006-2008, Fabien Fleutot <metalua@gmail.com>.
--
-- This software is released under the MIT Licence, see licence.txt
-- for details.
--
----------------------------------------------------------------------
--------------------------------------------------------------------------------
--
-- Exported API:
--
-- Parser generators:
-- * [gg.sequence()]
-- * [gg.multisequence()]
-- * [gg.expr()]
-- * [gg.list()]
-- * [gg.onkeyword()]
-- * [gg.optkeyword()]
--
-- Other functions:
-- * [gg.parse_error()]
-- * [gg.make_parser()]
-- * [gg.is_parser()]
--
--------------------------------------------------------------------------------
module("gg", package.seeall)
-------------------------------------------------------------------------------
-- parser metatable, which maps __call to method parse, and adds some
-- error tracing boilerplate.
-------------------------------------------------------------------------------
local parser_metatable = { }
function parser_metatable.__call (parser, lx, ...)
--printf ("Call parser %q of type %q", parser.name or "?", parser.kind)
if mlc.metabugs then
return parser:parse (lx, ...)
--local x = parser:parse (lx, ...)
--printf ("Result of parser %q: %s",
-- parser.name or "?",
-- _G.table.tostring(x, "nohash", 80))
--return x
else
local li = lx:lineinfo_right() or { "?", "?", "?", "?" }
local status, ast = pcall (parser.parse, parser, lx, ...)
if status then return ast else
-- Try to replace the gg.lua location, in the error msg, with
-- the place where the current parser started handling the
-- lexstream.
-- Since the error is rethrown, these places are stacked.
error (string.format ("%s\n - (l.%s, c.%s, k.%s) in parser %s",
ast :strmatch "gg.lua:%d+: (.*)" or ast,
li[1], li[2], li[3], parser.name or parser.kind))
end
end
end
-------------------------------------------------------------------------------
-- Turn a table into a parser, mainly by setting the metatable.
-------------------------------------------------------------------------------
function make_parser(kind, p)
p.kind = kind
if not p.transformers then p.transformers = { } end
function p.transformers:add (x)
table.insert (self, x)
end
setmetatable (p, parser_metatable)
return p
end
-------------------------------------------------------------------------------
-- Return true iff [x] is a parser.
-- If it's a gg-generated parser, return the name of its kind.
-------------------------------------------------------------------------------
function is_parser (x)
return type(x)=="function" or getmetatable(x)==parser_metatable and x.kind
end
-------------------------------------------------------------------------------
-- Parse a sequence, without applying builder nor transformers
-------------------------------------------------------------------------------
local function raw_parse_sequence (lx, p)
local r = { }
for i=1, #p do
e=p[i]
if type(e) == "string" then
if not lx:is_keyword (lx:next(), e) then
parse_error (lx, "A keyword was expected, probably `%s'.", e) end
elseif is_parser (e) then
table.insert (r, e (lx))
else
gg.parse_error (lx,"Sequence `%s': element #%i is neither a string "..
"nor a parser: %s",
p.name, i, table.tostring(e))
end
end
return r
end
-------------------------------------------------------------------------------
-- Parse a multisequence, without applying multisequence transformers.
-- The sequences are completely parsed.
-------------------------------------------------------------------------------
local function raw_parse_multisequence (lx, sequence_table, default)
local seq_parser = sequence_table[lx:is_keyword(lx:peek())]
if seq_parser then return seq_parser (lx)
elseif default then return default (lx)
else return false end
end
-------------------------------------------------------------------------------
-- Applies all transformers listed in parser on ast.
-------------------------------------------------------------------------------
local function transform (ast, parser, fli, lli)
if parser.transformers then
for _, t in ipairs (parser.transformers) do ast = t(ast) or ast end
end
if type(ast) == 'table'then
local ali = ast.lineinfo
if not ali or ali.first~=fli or ali.last~=lli then
ast.lineinfo = { first = fli, last = lli }
end
end
return ast
end
-------------------------------------------------------------------------------
-- Generate a tracable parsing error (not implemented yet)
-------------------------------------------------------------------------------
function parse_error(lx, fmt, ...)
local li = lx:lineinfo_left() or {-1,-1,-1, "<unknown file>"}
local msg = string.format("line %i, char %i: "..fmt, li[1], li[2], ...)
local src = lx.src
if li[3]>0 and src then
local i, j = li[3], li[3]
while src:sub(i,i) ~= '\n' and i>=0 do i=i-1 end
while src:sub(j,j) ~= '\n' and j<=#src do j=j+1 end
local srcline = src:sub (i+1, j-1)
local idx = string.rep (" ", li[2]).."^"
msg = string.format("%s\n>>> %s\n>>> %s", msg, srcline, idx)
end
error(msg)
end
-------------------------------------------------------------------------------
--
-- Sequence parser generator
--
-------------------------------------------------------------------------------
-- Input fields:
--
-- * [builder]: how to build an AST out of sequence parts. let [x] be the list
-- of subparser results (keywords are simply omitted). [builder] can be:
-- - [nil], in which case the result of parsing is simply [x]
-- - a string, which is then put as a tag on [x]
-- - a function, which takes [x] as a parameter and returns an AST.
--
-- * [name]: the name of the parser. Used for debug messages
--
-- * [transformers]: a list of AST->AST functions, applied in order on ASTs
-- returned by the parser.
--
-- * Table-part entries corresponds to keywords (strings) and subparsers
-- (function and callable objects).
--
-- After creation, the following fields are added:
-- * [parse] the parsing function lexer->AST
-- * [kind] == "sequence"
-- * [name] is set, if it wasn't in the input.
--
-------------------------------------------------------------------------------
function sequence (p)
make_parser ("sequence", p)
-------------------------------------------------------------------
-- Parsing method
-------------------------------------------------------------------
function p:parse (lx)
-- Raw parsing:
local fli = lx:lineinfo_right()
local seq = raw_parse_sequence (lx, self)
local lli = lx:lineinfo_left()
-- Builder application:
local builder, tb = self.builder, type (self.builder)
if tb == "string" then seq.tag = builder
elseif tb == "function" or builder and builder.__call then seq = builder(seq)
elseif builder == nil then -- nothing
else error ("Invalid builder of type "..tb.." in sequence") end
seq = transform (seq, self, fli, lli)
assert (not seq or seq.lineinfo)
return seq
end
-------------------------------------------------------------------
-- Construction
-------------------------------------------------------------------
-- Try to build a proper name
if p.name then
-- don't touch existing name
elseif type(p[1])=="string" then -- find name based on 1st keyword
if #p==1 then p.name=p[1]
elseif type(p[#p])=="string" then
p.name = p[1] .. " ... " .. p[#p]
else p.name = p[1] .. " ..." end
else -- can't find a decent name
p.name = "<anonymous>"
end
return p
end --</sequence>
-------------------------------------------------------------------------------
--
-- Multiple, keyword-driven, sequence parser generator
--
-------------------------------------------------------------------------------
-- in [p], useful fields are:
--
-- * [transformers]: as usual
--
-- * [name]: as usual
--
-- * Table-part entries must be sequence parsers, or tables which can
-- be turned into a sequence parser by [gg.sequence]. These
-- sequences must start with a keyword, and this initial keyword
-- must be different for each sequence. The table-part entries will
-- be removed after [gg.multisequence] returns.
--
-- * [default]: the parser to run if the next keyword in the lexer is
-- none of the registered initial keywords. If there's no default
-- parser and no suitable initial keyword, the multisequence parser
-- simply returns [false].
--
-- After creation, the following fields are added:
--
-- * [parse] the parsing function lexer->AST
--
-- * [sequences] the table of sequences, indexed by initial keywords.
--
-- * [add] method takes a sequence parser or a config table for
-- [gg.sequence], and adds/replaces the corresponding sequence
-- parser. If the keyword was already used, the former sequence is
-- removed and a warning is issued.
--
-- * [get] method returns a sequence by its initial keyword
--
-- * [kind] == "multisequence"
--
-------------------------------------------------------------------------------
function multisequence (p)
make_parser ("multisequence", p)
-------------------------------------------------------------------
-- Add a sequence (might be just a config table for [gg.sequence])
-------------------------------------------------------------------
function p:add (s)
-- compile if necessary:
local keyword = type(s)=='table' and s[1]
if type(s)=='table' and not is_parser(s) then sequence(s) end
if is_parser(s)~='sequence' or type(keyword)~='string' then
if self.default then -- two defaults
error ("In a multisequence parser, all but one sequences "..
"must start with a keyword")
else self.default = s end -- first default
elseif self.sequences[keyword] then -- duplicate keyword
eprintf (" *** Warning: keyword %q overloaded in multisequence ***",
keyword)
self.sequences[keyword] = s
else -- newly caught keyword
self.sequences[keyword] = s
end
end -- </multisequence.add>
-------------------------------------------------------------------
-- Get the sequence starting with this keyword. [kw :: string]
-------------------------------------------------------------------
function p:get (kw) return self.sequences [kw] end
-------------------------------------------------------------------
-- Remove the sequence starting with keyword [kw :: string]
-------------------------------------------------------------------
function p:del (kw)
if not self.sequences[kw] then
eprintf("*** Warning: trying to delete sequence starting "..
"with %q from a multisequence having no such "..
"entry ***", kw) end
local removed = self.sequences[kw]
self.sequences[kw] = nil
return removed
end
-------------------------------------------------------------------
-- Parsing method
-------------------------------------------------------------------
function p:parse (lx)
local fli = lx:lineinfo_right()
local x = raw_parse_multisequence (lx, self.sequences, self.default)
local lli = lx:lineinfo_left()
return transform (x, self, fli, lli)
end
-------------------------------------------------------------------
-- Construction
-------------------------------------------------------------------
-- Register the sequences passed to the constructor. They're going
-- from the array part of the parser to the hash part of field
-- [sequences]
p.sequences = { }
for i=1, #p do p:add (p[i]); p[i] = nil end
-- FIXME: why is this commented out?
--if p.default and not is_parser(p.default) then sequence(p.default) end
return p
end --</multisequence>
-------------------------------------------------------------------------------
--
-- Expression parser generator
--
-------------------------------------------------------------------------------
--
-- Expression configuration relies on three tables: [prefix], [infix]
-- and [suffix]. Moreover, the primary parser can be replaced by a
-- table: in this case the [primary] table will be passed to
-- [gg.multisequence] to create a parser.
--
-- Each of these tables is a modified multisequence parser: the
-- differences with respect to regular multisequence config tables are:
--
-- * the builder takes specific parameters:
-- - for [prefix], it takes the result of the prefix sequence parser,
-- and the prefixed expression
-- - for [infix], it takes the left-hand-side expression, the results
-- of the infix sequence parser, and the right-hand-side expression.
-- - for [suffix], it takes the suffixed expression, and theresult
-- of the suffix sequence parser.
--
-- * the default field is a list, with parameters:
-- - [parser] the raw parsing function
-- - [transformers], as usual
-- - [prec], the operator's precedence
-- - [assoc] for [infix] table, the operator's associativity, which
-- can be "left", "right" or "flat" (default to left)
--
-- In [p], useful fields are:
-- * [transformers]: as usual
-- * [name]: as usual
-- * [primary]: the atomic expression parser, or a multisequence config
-- table (mandatory)
-- * [prefix]: prefix operators config table, see above.
-- * [infix]: infix operators config table, see above.
-- * [suffix]: suffix operators config table, see above.
--
-- After creation, these fields are added:
-- * [kind] == "expr"
-- * [parse] as usual
-- * each table is turned into a multisequence, and therefore has an
-- [add] method
--
-------------------------------------------------------------------------------
function expr (p)
make_parser ("expr", p)
-------------------------------------------------------------------
-- parser method.
-- In addition to the lexer, it takes an optional precedence:
-- it won't read expressions whose precedence is lower or equal
-- to [prec].
-------------------------------------------------------------------
function p:parse (lx, prec)
prec = prec or 0
------------------------------------------------------
-- Extract the right parser and the corresponding
-- options table, for (pre|in|suff)fix operators.
-- Options include prec, assoc, transformers.
------------------------------------------------------
local function get_parser_info (tab)
local p2 = tab:get (lx:is_keyword (lx:peek()))
if p2 then -- keyword-based sequence found
local function parser(lx) return raw_parse_sequence(lx, p2) end
return parser, p2
else -- Got to use the default parser
local d = tab.default
if d then return d.parse or d.parser, d
else return false, false end
end
end
------------------------------------------------------
-- Look for a prefix sequence. Multiple prefixes are
-- handled through the recursive [p.parse] call.
-- Notice the double-transform: one for the primary
-- expr, and one for the one with the prefix op.
------------------------------------------------------
local function handle_prefix ()
local fli = lx:lineinfo_right()
local p2_func, p2 = get_parser_info (self.prefix)
local op = p2_func and p2_func (lx)
if op then -- Keyword-based sequence found
local ili = lx:lineinfo_right() -- Intermediate LineInfo
local e = p2.builder (op, self:parse (lx, p2.prec))
local lli = lx:lineinfo_left()
return transform (transform (e, p2, ili, lli), self, fli, lli)
else -- No prefix found, get a primary expression
local e = self.primary(lx)
local lli = lx:lineinfo_left()
return transform (e, self, fli, lli)
end
end --</expr.parse.handle_prefix>
------------------------------------------------------
-- Look for an infix sequence+right-hand-side operand.
-- Return the whole binary expression result,
-- or false if no operator was found.
------------------------------------------------------
local function handle_infix (e)
local p2_func, p2 = get_parser_info (self.infix)
if not p2 then return false end
-----------------------------------------
-- Handle flattening operators: gather all operands
-- of the series in [list]; when a different operator
-- is found, stop, build from [list], [transform] and
-- return.
-----------------------------------------
if (not p2.prec or p2.prec>prec) and p2.assoc=="flat" then
local fli = lx:lineinfo_right()
local pflat, list = p2, { e }
repeat
local op = p2_func(lx)
if not op then break end
table.insert (list, self:parse (lx, p2.prec))
local _ -- We only care about checking that p2==pflat
_, p2 = get_parser_info (self.infix)
until p2 ~= pflat
local e2 = pflat.builder (list)
local lli = lx:lineinfo_left()
return transform (transform (e2, pflat, fli, lli), self, fli, lli)
-----------------------------------------
-- Handle regular infix operators: [e] the LHS is known,
-- just gather the operator and [e2] the RHS.
-- Result goes in [e3].
-----------------------------------------
elseif p2.prec and p2.prec>prec or
p2.prec==prec and p2.assoc=="right" then
local fli = e.lineinfo.first -- lx:lineinfo_right()
local op = p2_func(lx)
if not op then return false end
local e2 = self:parse (lx, p2.prec)
local e3 = p2.builder (e, op, e2)
local lli = lx:lineinfo_left()
return transform (transform (e3, p2, fli, lli), self, fli, lli)
-----------------------------------------
-- Check for non-associative operators, and complain if applicable.
-----------------------------------------
elseif p2.assoc=="none" and p2.prec==prec then
parse_error (lx, "non-associative operator!")
-----------------------------------------
-- No infix operator suitable at that precedence
-----------------------------------------
else return false end
end --</expr.parse.handle_infix>
------------------------------------------------------
-- Look for a suffix sequence.
-- Return the result of suffix operator on [e],
-- or false if no operator was found.
------------------------------------------------------
local function handle_suffix (e)
-- FIXME bad fli, must take e.lineinfo.first
local p2_func, p2 = get_parser_info (self.suffix)
if not p2 then return false end
if not p2.prec or p2.prec>=prec then
--local fli = lx:lineinfo_right()
local fli = e.lineinfo.first
local op = p2_func(lx)
if not op then return false end
local lli = lx:lineinfo_left()
e = p2.builder (e, op)
e = transform (transform (e, p2, fli, lli), self, fli, lli)
return e
end
return false
end --</expr.parse.handle_suffix>
------------------------------------------------------
-- Parser body: read suffix and (infix+operand)
-- extensions as long as we're able to fetch more at
-- this precedence level.
------------------------------------------------------
local e = handle_prefix()
repeat
local x = handle_suffix (e); e = x or e
local y = handle_infix (e); e = y or e
until not (x or y)
-- No transform: it already happened in operators handling
return e
end --</expr.parse>
-------------------------------------------------------------------
-- Construction
-------------------------------------------------------------------
if not p.primary then p.primary=p[1]; p[1]=nil end
for _, t in ipairs{ "primary", "prefix", "infix", "suffix" } do
if not p[t] then p[t] = { } end
if not is_parser(p[t]) then multisequence(p[t]) end
end
function p:add(...) return self.primary:add(...) end
return p
end --</expr>
-------------------------------------------------------------------------------
--
-- List parser generator
--
-------------------------------------------------------------------------------
-- In [p], the following fields can be provided in input:
--
-- * [builder]: takes list of subparser results, returns AST
-- * [transformers]: as usual
-- * [name]: as usual
--
-- * [terminators]: list of strings representing the keywords which
-- might mark the end of the list. When non-empty, the list is
-- allowed to be empty. A string is treated as a single-element
-- table, whose element is that string, e.g. ["do"] is the same as
-- [{"do"}].
--
-- * [separators]: list of strings representing the keywords which can
-- separate elements of the list. When non-empty, one of these
-- keyword has to be found between each element. Lack of a separator
-- indicates the end of the list. A string is treated as a
-- single-element table, whose element is that string, e.g. ["do"]
-- is the same as [{"do"}]. If [terminators] is empty/nil, then
-- [separators] has to be non-empty.
--
-- After creation, the following fields are added:
-- * [parse] the parsing function lexer->AST
-- * [kind] == "list"
--
-------------------------------------------------------------------------------
function list (p)
make_parser ("list", p)
-------------------------------------------------------------------
-- Parsing method
-------------------------------------------------------------------
function p:parse (lx)
------------------------------------------------------
-- Used to quickly check whether there's a terminator
-- or a separator immediately ahead
------------------------------------------------------
local function peek_is_in (keywords)
return keywords and lx:is_keyword(lx:peek(), unpack(keywords)) end
local x = { }
local fli = lx:lineinfo_right()
-- if there's a terminator to start with, don't bother trying
if not peek_is_in (self.terminators) then
repeat table.insert (x, self.primary (lx)) -- read one element
until
-- First reason to stop: There's a separator list specified,
-- and next token isn't one. Otherwise, consume it with [lx:next()]
self.separators and not(peek_is_in (self.separators) and lx:next()) or
-- Other reason to stop: terminator token ahead
peek_is_in (self.terminators) or
-- Last reason: end of file reached
lx:peek().tag=="Eof"
end
local lli = lx:lineinfo_left()
-- Apply the builder. It can be a string, or a callable value,
-- or simply nothing.
local b = self.builder
if b then
if type(b)=="string" then x.tag = b -- b is a string, use it as a tag
elseif type(b)=="function" then x=b(x)
else
local bmt = getmetatable(b)
if bmt and bmt.__call then x=b(x) end
end
end
return transform (x, self, fli, lli)
end --</list.parse>
-------------------------------------------------------------------
-- Construction
-------------------------------------------------------------------
if not p.primary then p.primary = p[1]; p[1] = nil end
if type(p.terminators) == "string" then p.terminators = { p.terminators }
elseif p.terminators and #p.terminators == 0 then p.terminators = nil end
if type(p.separators) == "string" then p.separators = { p.separators }
elseif p.separators and #p.separators == 0 then p.separators = nil end
return p
end --</list>
-------------------------------------------------------------------------------
--
-- Keyword-conditionned parser generator
--
-------------------------------------------------------------------------------
--
-- Only apply a parser if a given keyword is found. The result of
-- [gg.onkeyword] parser is the result of the subparser (modulo
-- [transformers] applications).
--
-- lineinfo: the keyword is *not* included in the boundaries of the
-- resulting lineinfo. A review of all usages of gg.onkeyword() in the
-- implementation of metalua has shown that it was the appropriate choice
-- in every case.
--
-- Input fields:
--
-- * [name]: as usual
--
-- * [transformers]: as usual
--
-- * [peek]: if non-nil, the conditionning keyword is left in the lexeme
-- stream instead of being consumed.
--
-- * [primary]: the subparser.
--
-- * [keywords]: list of strings representing triggering keywords.
--
-- * Table-part entries can contain strings, and/or exactly one parser.
-- Strings are put in [keywords], and the parser is put in [primary].
--
-- After the call, the following fields will be set:
--
-- * [parse] the parsing method
-- * [kind] == "onkeyword"
-- * [primary]
-- * [keywords]
--
-------------------------------------------------------------------------------
function onkeyword (p)
make_parser ("onkeyword", p)
-------------------------------------------------------------------
-- Parsing method
-------------------------------------------------------------------
function p:parse(lx)
if lx:is_keyword (lx:peek(), unpack(self.keywords)) then
--local fli = lx:lineinfo_right()
if not self.peek then lx:next() end
local content = self.primary (lx)
--local lli = lx:lineinfo_left()
local fli, lli = content.lineinfo.first, content.lineinfo.last
return transform (content, p, fli, lli)
else return false end
end
-------------------------------------------------------------------
-- Construction
-------------------------------------------------------------------
if not p.keywords then p.keywords = { } end
for _, x in ipairs(p) do
if type(x)=="string" then table.insert (p.keywords, x)
else assert (not p.primary and is_parser (x)); p.primary = x end
end
if not next (p.keywords) then
eprintf("Warning, no keyword to trigger gg.onkeyword") end
assert (p.primary, 'no primary parser in gg.onkeyword')
return p
end --</onkeyword>
-------------------------------------------------------------------------------
--
-- Optional keyword consummer pseudo-parser generator
--
-------------------------------------------------------------------------------
--
-- This doesn't return a real parser, just a function. That function parses
-- one of the keywords passed as parameters, and returns it. It returns
-- [false] if no matching keyword is found.
--
-- Notice that tokens returned by lexer already carry lineinfo, therefore
-- there's no need to add them, as done usually through transform() calls.
-------------------------------------------------------------------------------
function optkeyword (...)
local args = {...}
if type (args[1]) == "table" then
assert (#args == 1)
args = args[1]
end
for _, v in ipairs(args) do assert (type(v)=="string") end
return function (lx)
local x = lx:is_keyword (lx:peek(), unpack (args))
if x then lx:next(); return x
else return false end
end
end
-------------------------------------------------------------------------------
--
-- Run a parser with a special lexer
--
-------------------------------------------------------------------------------
--
-- This doesn't return a real parser, just a function.
-- First argument is the lexer class to be used with the parser,
-- 2nd is the parser itself.
-- The resulting parser returns whatever the argument parser does.
--
-------------------------------------------------------------------------------
function with_lexer(new_lexer, parser)
-------------------------------------------------------------------
-- Most gg functions take their parameters in a table, so it's
-- better to silently accept when with_lexer{ } is called with
-- its arguments in a list:
-------------------------------------------------------------------
if not parser and #new_lexer==2 and type(new_lexer[1])=='table' then
return with_lexer(unpack(new_lexer))
end
-------------------------------------------------------------------
-- Save the current lexer, switch it for the new one, run the parser,
-- restore the previous lexer, even if the parser caused an error.
-------------------------------------------------------------------
return function (lx)
local old_lexer = getmetatable(lx)
lx:sync()
setmetatable(lx, new_lexer)
local status, result = pcall(parser, lx)
lx:sync()
setmetatable(lx, old_lexer)
if status then return result else error(result) end
end
end

1034
lualibs/metalua/lcode.lua Normal file

File diff suppressed because it is too large Load Diff

441
lualibs/metalua/ldump.lua Normal file
View File

@@ -0,0 +1,441 @@
----------------------------------------------------------------------
--
-- WARNING! You're entering a hackish area, proceed at your own risks!
--
-- This code results from the borrowing, then ruthless abuse, of
-- Yueliang's implementation of Lua 5.0 compiler. I claim
-- responsibility for all of the ugly, dirty stuff that you might spot
-- in it.
--
-- Eventually, this code will be rewritten, either in Lua or more
-- probably in C. Meanwhile, if you're interested into digging
-- metalua's sources, this is not the best part to invest your time
-- on.
--
-- End of warning.
--
----------------------------------------------------------------------
--[[--------------------------------------------------------------------
$Id$
ldump.lua
Save bytecodes in Lua
This file is part of Yueliang.
Copyright (c) 2005 Kein-Hong Man <khman@users.sf.net>
The COPYRIGHT file describes the conditions
under which this software may be distributed.
------------------------------------------------------------------------
[FF] Slightly modified, mainly to produce Lua 5.1 bytecode.
----------------------------------------------------------------------]]
--[[--------------------------------------------------------------------
-- Notes:
-- * LUA_NUMBER (double), byte order (little endian) and some other
-- header values hard-coded; see other notes below...
-- * One significant difference is that instructions are still in table
-- form (with OP/A/B/C/Bx fields) and luaP:Instruction() is needed to
-- convert them into 4-char strings
-- * Deleted:
-- luaU:DumpVector: folded into DumpLines, DumpCode
-- * Added:
-- luaU:endianness() (from lundump.c)
-- luaU:make_setS: create a chunk writer that writes to a string
-- luaU:make_setF: create a chunk writer that writes to a file
-- (lua.h contains a typedef for a Chunkwriter pointer, and
-- a Lua-based implementation exists, writer() in lstrlib.c)
-- luaU:from_double(x): encode double value for writing
-- luaU:from_int(x): encode integer value for writing
-- (error checking is limited for these conversion functions)
-- (double conversion does not support denormals or NaNs)
-- luaU:ttype(o) (from lobject.h)
----------------------------------------------------------------------]]
module("bytecode", package.seeall)
format = { }
format.header = string.dump(function()end):sub(1, 12)
format.little_endian, format.int_size,
format.size_t_size, format.instr_size,
format.number_size, format.integral = format.header:byte(7, 12)
format.little_endian = format.little_endian~=0
format.integral = format.integral ~=0
assert(format.integral or format.number_size==8, "Number format not supported by dumper")
assert(format.little_endian, "Big endian architectures not supported by dumper")
--requires luaP
luaU = {}
-- constants used by dumper
luaU.LUA_TNIL = 0
luaU.LUA_TBOOLEAN = 1
luaU.LUA_TNUMBER = 3 -- (all in lua.h)
luaU.LUA_TSTRING = 4
luaU.LUA_TNONE = -1
-- definitions for headers of binary files
--luaU.LUA_SIGNATURE = "\27Lua" -- binary files start with "<esc>Lua"
--luaU.VERSION = 81 -- 0x50; last format change was in 5.0
--luaU.FORMAT_VERSION = 0 -- 0 is official version. yeah I know I'm a liar.
-- a multiple of PI for testing native format
-- multiplying by 1E7 gives non-trivial integer values
--luaU.TEST_NUMBER = 3.14159265358979323846E7
--[[--------------------------------------------------------------------
-- Additional functions to handle chunk writing
-- * to use make_setS and make_setF, see test_ldump.lua elsewhere
----------------------------------------------------------------------]]
------------------------------------------------------------------------
-- works like the lobject.h version except that TObject used in these
-- scripts only has a 'value' field, no 'tt' field (native types used)
------------------------------------------------------------------------
function luaU:ttype(o)
local tt = type(o.value)
if tt == "number" then return self.LUA_TNUMBER
elseif tt == "string" then return self.LUA_TSTRING
elseif tt == "nil" then return self.LUA_TNIL
elseif tt == "boolean" then return self.LUA_TBOOLEAN
else
return self.LUA_TNONE -- the rest should not appear
end
end
------------------------------------------------------------------------
-- create a chunk writer that writes to a string
-- * returns the writer function and a table containing the string
-- * to get the final result, look in buff.data
------------------------------------------------------------------------
function luaU:make_setS()
local buff = {}
buff.data = ""
local writer =
function(s, buff) -- chunk writer
if not s then return end
buff.data = buff.data..s
end
return writer, buff
end
------------------------------------------------------------------------
-- create a chunk writer that writes to a file
-- * returns the writer function and a table containing the file handle
-- * if a nil is passed, then writer should close the open file
------------------------------------------------------------------------
function luaU:make_setF(filename)
local buff = {}
buff.h = io.open(filename, "wb")
if not buff.h then return nil end
local writer =
function(s, buff) -- chunk writer
if not buff.h then return end
if not s then buff.h:close(); return end
buff.h:write(s)
end
return writer, buff
end
-----------------------------------------------------------------------
-- converts a IEEE754 double number to an 8-byte little-endian string
-- * luaU:from_double() and luaU:from_int() are from ChunkBake project
-- * supports +/- Infinity, but not denormals or NaNs
-----------------------------------------------------------------------
function luaU:from_double(x)
local function grab_byte(v)
return math.floor(v / 256),
string.char(math.mod(math.floor(v), 256))
end
local sign = 0
if x < 0 then sign = 1; x = -x end
local mantissa, exponent = math.frexp(x)
if x == 0 then -- zero
mantissa, exponent = 0, 0
elseif x == 1/0 then
mantissa, exponent = 0, 2047
else
mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, 53)
exponent = exponent + 1022
end
local v, byte = "" -- convert to bytes
x = mantissa
for i = 1,6 do
x, byte = grab_byte(x); v = v..byte -- 47:0
end
x, byte = grab_byte(exponent * 16 + x); v = v..byte -- 55:48
x, byte = grab_byte(sign * 128 + x); v = v..byte -- 63:56
return v
end
-----------------------------------------------------------------------
-- converts a number to a little-endian 32-bit integer string
-- * input value assumed to not overflow, can be signed/unsigned
-----------------------------------------------------------------------
function luaU:from_int(x, size)
local v = ""
x = math.floor(x)
if x >= 0 then
for i = 1, size do
v = v..string.char(math.mod(x, 256)); x = math.floor(x / 256)
end
else -- x < 0
x = -x
local carry = 1
for i = 1, size do
local c = 255 - math.mod(x, 256) + carry
if c == 256 then c = 0; carry = 1 else carry = 0 end
v = v..string.char(c); x = math.floor(x / 256)
end
end
return v
end
--[[--------------------------------------------------------------------
-- Functions to make a binary chunk
-- * many functions have the size parameter removed, since output is
-- in the form of a string and some sizes are implicit or hard-coded
-- * luaU:DumpVector has been deleted (used in DumpCode & DumpLines)
----------------------------------------------------------------------]]
------------------------------------------------------------------------
-- dump a block of literal bytes
------------------------------------------------------------------------
function luaU:DumpLiteral(s, D) self:DumpBlock(s, D) end
--[[--------------------------------------------------------------------
-- struct DumpState:
-- L -- lua_State (not used in this script)
-- write -- lua_Chunkwriter (chunk writer function)
-- data -- void* (chunk writer context or data already written)
----------------------------------------------------------------------]]
------------------------------------------------------------------------
-- dumps a block of bytes
-- * lua_unlock(D.L), lua_lock(D.L) deleted
------------------------------------------------------------------------
function luaU:DumpBlock(b, D) D.write(b, D.data) end
------------------------------------------------------------------------
-- dumps a single byte
------------------------------------------------------------------------
function luaU:DumpByte(y, D)
self:DumpBlock(string.char(y), D)
end
------------------------------------------------------------------------
-- dumps a 32-bit signed integer (for int)
------------------------------------------------------------------------
function luaU:DumpInt(x, D)
self:DumpBlock(self:from_int(x, format.int_size), D)
end
------------------------------------------------------------------------
-- dumps a 32-bit unsigned integer (for size_t)
------------------------------------------------------------------------
function luaU:DumpSize(x, D)
self:DumpBlock(self:from_int(x, format.size_t_size), D)
end
------------------------------------------------------------------------
-- dumps a LUA_NUMBER (hard-coded as a double)
------------------------------------------------------------------------
function luaU:DumpNumber(x, D)
if format.integral then
self:DumpBlock(self:from_int(x, format.number_size), D)
else
self:DumpBlock(self:from_double(x), D)
end
end
------------------------------------------------------------------------
-- dumps a Lua string
------------------------------------------------------------------------
function luaU:DumpString(s, D)
if s == nil then
self:DumpSize(0, D)
else
s = s.."\0" -- include trailing '\0'
self:DumpSize(string.len(s), D)
self:DumpBlock(s, D)
end
end
------------------------------------------------------------------------
-- dumps instruction block from function prototype
------------------------------------------------------------------------
function luaU:DumpCode(f, D)
local n = f.sizecode
self:DumpInt(n, D)
--was DumpVector
for i = 0, n - 1 do
self:DumpBlock(luaP:Instruction(f.code[i]), D)
end
end
------------------------------------------------------------------------
-- dumps local variable names from function prototype
------------------------------------------------------------------------
function luaU:DumpLocals(f, D)
local n = f.sizelocvars
self:DumpInt(n, D)
for i = 0, n - 1 do
-- Dirty temporary fix:
-- `Stat{ } keeps properly count of the number of local vars,
-- but fails to keep score of their debug info (names).
-- It therefore might happen that #f.localvars < f.sizelocvars, or
-- that a variable's startpc and endpc fields are left unset.
-- FIXME: This might not be needed anymore, check the bug report
-- by J. Belmonte.
local var = f.locvars[i]
if not var then break end
-- printf("[DUMPLOCALS] dumping local var #%i = %s", i, table.tostring(var))
self:DumpString(var.varname, D)
self:DumpInt(var.startpc or 0, D)
self:DumpInt(var.endpc or 0, D)
end
end
------------------------------------------------------------------------
-- dumps line information from function prototype
------------------------------------------------------------------------
function luaU:DumpLines(f, D)
local n = f.sizelineinfo
self:DumpInt(n, D)
--was DumpVector
for i = 0, n - 1 do
self:DumpInt(f.lineinfo[i], D) -- was DumpBlock
--print(i, f.lineinfo[i])
end
end
------------------------------------------------------------------------
-- dump upvalue names from function prototype
------------------------------------------------------------------------
function luaU:DumpUpvalues(f, D)
local n = f.sizeupvalues
self:DumpInt(n, D)
for i = 0, n - 1 do
self:DumpString(f.upvalues[i], D)
end
end
------------------------------------------------------------------------
-- dump constant pool from function prototype
-- * nvalue(o) and tsvalue(o) macros removed
------------------------------------------------------------------------
function luaU:DumpConstants(f, D)
local n = f.sizek
self:DumpInt(n, D)
for i = 0, n - 1 do
local o = f.k[i] -- TObject
local tt = self:ttype(o)
assert (tt >= 0)
self:DumpByte(tt, D)
if tt == self.LUA_TNUMBER then
self:DumpNumber(o.value, D)
elseif tt == self.LUA_TSTRING then
self:DumpString(o.value, D)
elseif tt == self.LUA_TBOOLEAN then
self:DumpByte (o.value and 1 or 0, D)
elseif tt == self.LUA_TNIL then
else
assert(false) -- cannot happen
end
end
end
function luaU:DumpProtos (f, D)
local n = f.sizep
assert (n)
self:DumpInt(n, D)
for i = 0, n - 1 do
self:DumpFunction(f.p[i], f.source, D)
end
end
function luaU:DumpDebug(f, D)
self:DumpLines(f, D)
self:DumpLocals(f, D)
self:DumpUpvalues(f, D)
end
------------------------------------------------------------------------
-- dump child function prototypes from function prototype
--FF completely reworked for 5.1 format
------------------------------------------------------------------------
function luaU:DumpFunction(f, p, D)
-- print "Dumping function:"
-- table.print(f, 60)
local source = f.source
if source == p then source = nil end
self:DumpString(source, D)
self:DumpInt(f.lineDefined, D)
self:DumpInt(f.lastLineDefined or 42, D)
self:DumpByte(f.nups, D)
self:DumpByte(f.numparams, D)
self:DumpByte(f.is_vararg, D)
self:DumpByte(f.maxstacksize, D)
self:DumpCode(f, D)
self:DumpConstants(f, D)
self:DumpProtos( f, D)
self:DumpDebug(f, D)
end
------------------------------------------------------------------------
-- dump Lua header section (some sizes hard-coded)
--FF: updated for version 5.1
------------------------------------------------------------------------
function luaU:DumpHeader(D)
self:DumpLiteral(format.header, D)
end
------------------------------------------------------------------------
-- dump function as precompiled chunk
-- * w, data are created from make_setS, make_setF
--FF: suppressed extraneous [L] param
------------------------------------------------------------------------
function luaU:dump (Main, w, data)
local D = {} -- DumpState
D.write = w
D.data = data
self:DumpHeader(D)
self:DumpFunction(Main, nil, D)
-- added: for a chunk writer writing to a file, this final call with
-- nil data is to indicate to the writer to close the file
D.write(nil, D.data)
end
------------------------------------------------------------------------
-- find byte order (from lundump.c)
-- * hard-coded to little-endian
------------------------------------------------------------------------
function luaU:endianness()
return 1
end
-- FIXME: ugly concat-base generation in [make_setS], bufferize properly!
function dump_string (proto)
local writer, buff = luaU:make_setS()
luaU:dump (proto, writer, buff)
return buff.data
end
-- FIXME: [make_setS] sucks, perform synchronous file writing
-- Now unused
function dump_file (proto, filename)
local writer, buff = luaU:make_setS()
luaU:dump (proto, writer, buff)
local file = io.open (filename, "wb")
file:write (buff.data)
io.close(file)
if UNIX_SHARPBANG then os.execute ("chmod a+x "..filename) end
end

511
lualibs/metalua/lexer.lua Normal file
View File

@@ -0,0 +1,511 @@
----------------------------------------------------------------------
-- Metalua: $Id: mll.lua,v 1.3 2006/11/15 09:07:50 fab13n Exp $
--
-- Summary: generic Lua-style lexer definition. You need this plus
-- some keyword additions to create the complete Lua lexer,
-- as is done in mlp_lexer.lua.
--
-- TODO:
--
-- * Make it easy to define new flavors of strings. Replacing the
-- lexer.patterns.long_string regexp by an extensible list, with
-- customizable token tag, would probably be enough. Maybe add:
-- + an index of capture for the regexp, that would specify
-- which capture holds the content of the string-like token
-- + a token tag
-- + or a string->string transformer function.
--
-- * There are some _G.table to prevent a namespace clash which has
-- now disappered. remove them.
----------------------------------------------------------------------
--
-- Copyright (c) 2006, Fabien Fleutot <metalua@gmail.com>.
--
-- This software is released under the MIT Licence, see licence.txt
-- for details.
--
----------------------------------------------------------------------
module ("lexer", package.seeall)
-- don't load metalua.runtime as it loads metalua.base, which pollutes
-- global namespace and overwrites pairs/ipairs -- PK 6/4/2012
require 'metalua.table2'
lexer = { alpha={ }, sym={ } }
lexer.__index=lexer
local debugf = function() end
--local debugf=printf
----------------------------------------------------------------------
-- Patterns used by [lexer:extract] to decompose the raw string into
-- correctly tagged tokens.
----------------------------------------------------------------------
lexer.patterns = {
spaces = "^[ \r\n\t]*()",
short_comment = "^%-%-([^\n]*)()\n",
final_short_comment = "^%-%-([^\n]*)()$",
long_comment = "^%-%-%[(=*)%[\n?(.-)%]%1%]()",
long_string = "^%[(=*)%[\n?(.-)%]%1%]()",
number_mantissa = { "^%d+%.?%d*()", "^%d*%.%d+()" },
number_exponant = "^[eE][%+%-]?%d+()",
number_hex = "^0[xX]%x+()",
word = "^([%a_][%w_]*)()"
}
----------------------------------------------------------------------
-- unescape a whole string, applying [unesc_digits] and
-- [unesc_letter] as many times as required.
----------------------------------------------------------------------
local function unescape_string (s)
-- Turn the digits of an escape sequence into the corresponding
-- character, e.g. [unesc_digits("123") == string.char(123)].
local function unesc_digits (backslashes, digits)
if #backslashes%2==0 then
-- Even number of backslashes, they escape each other, not the digits.
-- Return them so that unesc_letter() can treaat them
return backslashes..digits
else
-- Remove the odd backslash, which escapes the number sequence.
-- The rest will be returned and parsed by unesc_letter()
backslashes = backslashes :sub (1,-2)
end
local k, j, i = digits:reverse():byte(1, 3)
local z = _G.string.byte "0"
local code = (k or z) + 10*(j or z) + 100*(i or z) - 111*z
if code > 255 then
error ("Illegal escape sequence '\\"..digits..
"' in string: ASCII codes must be in [0..255]")
end
return backslashes .. string.char (code)
end
-- Take a letter [x], and returns the character represented by the
-- sequence ['\\'..x], e.g. [unesc_letter "n" == "\n"].
local function unesc_letter(x)
local t = {
a = "\a", b = "\b", f = "\f",
n = "\n", r = "\r", t = "\t", v = "\v",
["\\"] = "\\", ["'"] = "'", ['"'] = '"', ["\n"] = "\n" }
return t[x] or error([[Unknown escape sequence '\]]..x..[[']])
end
return s
:gsub ("(\\+)([0-9][0-9]?[0-9]?)", unesc_digits)
:gsub ("\\(%D)",unesc_letter)
end
lexer.extractors = {
"skip_whitespaces_and_comments",
"extract_short_string", "extract_word", "extract_number",
"extract_long_string", "extract_symbol" }
lexer.token_metatable = {
-- __tostring = function(a)
-- return string.format ("`%s{'%s'}",a.tag, a[1])
-- end
}
lexer.lineinfo_metatable = { }
----------------------------------------------------------------------
-- Really extract next token fron the raw string
-- (and update the index).
-- loc: offset of the position just after spaces and comments
-- previous_i: offset in src before extraction began
----------------------------------------------------------------------
function lexer:extract ()
local previous_i = self.i
local loc = self.i
local eof, token
-- Put line info, comments and metatable around the tag and content
-- provided by extractors, thus returning a complete lexer token.
-- first_line: line # at the beginning of token
-- first_column_offset: char # of the last '\n' before beginning of token
-- i: scans from beginning of prefix spaces/comments to end of token.
local function build_token (tag, content)
assert (tag and content)
local i, first_line, first_column_offset, previous_line_length =
previous_i, self.line, self.column_offset, nil
-- update self.line and first_line. i := indexes of '\n' chars
while true do
i = self.src :find ("\n", i+1, true)
if not i or i>self.i then break end -- no more '\n' until end of token
previous_line_length = i - self.column_offset
if loc and i <= loc then -- '\n' before beginning of token
first_column_offset = i
first_line = first_line+1
end
self.line = self.line+1
self.column_offset = i
end
-- lineinfo entries: [1]=line, [2]=column, [3]=char, [4]=filename
local fli = { first_line, loc-first_column_offset, loc, self.src_name }
local lli = { self.line, self.i-self.column_offset-1, self.i-1, self.src_name }
--Pluto barfes when the metatable is set:(
setmetatable(fli, lexer.lineinfo_metatable)
setmetatable(lli, lexer.lineinfo_metatable)
local a = { tag = tag, lineinfo = { first=fli, last=lli }, content }
if lli[2]==-1 then lli[1], lli[2] = lli[1]-1, previous_line_length-1 end
if #self.attached_comments > 0 then
a.lineinfo.comments = self.attached_comments
fli.comments = self.attached_comments
if self.lineinfo_last then
self.lineinfo_last.comments = self.attached_comments
end
end
self.attached_comments = { }
return setmetatable (a, self.token_metatable)
end --</function build_token>
for ext_idx, extractor in ipairs(self.extractors) do
-- printf("method = %s", method)
local tag, content = self [extractor] (self)
-- [loc] is placed just after the leading whitespaces and comments;
-- for this to work, the whitespace extractor *must be* at index 1.
if ext_idx==1 then loc = self.i end
if tag then
--printf("`%s{ %q }\t%i", tag, content, loc);
return build_token (tag, content)
end
end
error "None of the lexer extractors returned anything!"
end
----------------------------------------------------------------------
-- skip whites and comments
-- FIXME: doesn't take into account:
-- - unterminated long comments
-- - short comments at last line without a final \n
----------------------------------------------------------------------
function lexer:skip_whitespaces_and_comments()
local table_insert = _G.table.insert
repeat -- loop as long as a space or comment chunk is found
local _, j
local again = false
local last_comment_content = nil
-- skip spaces
self.i = self.src:match (self.patterns.spaces, self.i)
-- skip a long comment if any
_, last_comment_content, j =
self.src :match (self.patterns.long_comment, self.i)
if j then
table_insert(self.attached_comments,
{last_comment_content, self.i, j, "long"})
self.i=j; again=true
end
-- skip a short comment if any
last_comment_content, j = self.src:match (self.patterns.short_comment, self.i)
if j then
table_insert(self.attached_comments,
{last_comment_content, self.i, j, "short"})
self.i=j; again=true
end
if self.i>#self.src then return "Eof", "eof" end
until not again
if self.src:match (self.patterns.final_short_comment, self.i) then
return "Eof", "eof" end
--assert (not self.src:match(self.patterns.short_comment, self.i))
--assert (not self.src:match(self.patterns.long_comment, self.i))
-- --assert (not self.src:match(self.patterns.spaces, self.i))
return
end
----------------------------------------------------------------------
-- extract a '...' or "..." short string
----------------------------------------------------------------------
function lexer:extract_short_string()
-- [k] is the first unread char, [self.i] points to [k] in [self.src]
local j, k = self.i, self.src :sub (self.i,self.i)
if k~="'" and k~='"' then return end
local i = self.i + 1
local j = i
while true do
-- k = opening char: either simple-quote or double-quote
-- i = index of beginning-of-string
-- x = next "interesting" character
-- j = position after interesting char
-- y = char just after x
local x, y
x, j, y = self.src :match ("([\\\r\n"..k.."])()(.?)", j)
if x == '\\' then j=j+1 -- don't parse escaped char
elseif x == k then break -- unescaped end of string
else -- eof or '\r' or '\n' reached before end of string
assert (not x or x=="\r" or x=="\n")
error "Unterminated string"
end
end
self.i = j
return "String", unescape_string (self.src:sub (i,j-2))
end
----------------------------------------------------------------------
--
----------------------------------------------------------------------
function lexer:extract_word()
-- Id / keyword
local word, j = self.src:match (self.patterns.word, self.i)
if word then
self.i = j
if self.alpha [word] then return "Keyword", word
else return "Id", word end
end
end
----------------------------------------------------------------------
--
----------------------------------------------------------------------
function lexer:extract_number()
-- Number
local j = self.src:match(self.patterns.number_hex, self.i)
if not j then
j = self.src:match (self.patterns.number_mantissa[1], self.i) or
self.src:match (self.patterns.number_mantissa[2], self.i)
if j then
j = self.src:match (self.patterns.number_exponant, j) or j;
end
end
if not j then return end
-- Number found, interpret with tonumber() and return it
local n = tonumber (self.src:sub (self.i, j-1))
self.i = j
return "Number", n
end
----------------------------------------------------------------------
--
----------------------------------------------------------------------
function lexer:extract_long_string()
-- Long string
local _, content, j = self.src:match (self.patterns.long_string, self.i)
if j then self.i = j; return "String", content end
end
----------------------------------------------------------------------
--
----------------------------------------------------------------------
function lexer:extract_symbol()
-- compound symbol
local k = self.src:sub (self.i,self.i)
local symk = self.sym [k]
if not symk then
self.i = self.i + 1
return "Keyword", k
end
for _, sym in pairs (symk) do
if sym == self.src:sub (self.i, self.i + #sym - 1) then
self.i = self.i + #sym;
return "Keyword", sym
end
end
-- single char symbol
self.i = self.i+1
return "Keyword", k
end
----------------------------------------------------------------------
-- Add a keyword to the list of keywords recognized by the lexer.
----------------------------------------------------------------------
function lexer:add (w, ...)
assert(not ..., "lexer:add() takes only one arg, although possibly a table")
if type (w) == "table" then
for _, x in ipairs (w) do self:add (x) end
else
if w:match (self.patterns.word .. "$") then self.alpha [w] = true
elseif w:match "^%p%p+$" then
local k = w:sub(1,1)
local list = self.sym [k]
if not list then list = { }; self.sym [k] = list end
_G.table.insert (list, w)
elseif w:match "^%p$" then return
else error "Invalid keyword" end
end
end
----------------------------------------------------------------------
-- Return the [n]th next token, without consumming it.
-- [n] defaults to 1. If it goes pass the end of the stream, an EOF
-- token is returned.
----------------------------------------------------------------------
function lexer:peek (n)
if not n then n=1 end
if n > #self.peeked then
for i = #self.peeked+1, n do
self.peeked [i] = self:extract()
end
end
return self.peeked [n]
end
----------------------------------------------------------------------
-- Return the [n]th next token, removing it as well as the 0..n-1
-- previous tokens. [n] defaults to 1. If it goes pass the end of the
-- stream, an EOF token is returned.
----------------------------------------------------------------------
function lexer:next (n)
n = n or 1
self:peek (n)
local a
for i=1,n do
a = _G.table.remove (self.peeked, 1)
if a then
--debugf ("lexer:next() ==> %s %s",
-- table.tostring(a), tostring(a))
end
self.lastline = a.lineinfo.last[1]
end
self.lineinfo_last = a.lineinfo.last
return a or eof_token
end
----------------------------------------------------------------------
-- Returns an object which saves the stream's current state.
----------------------------------------------------------------------
-- FIXME there are more fields than that to save
function lexer:save () return { self.i; _G.table.cat(self.peeked) } end
----------------------------------------------------------------------
-- Restore the stream's state, as saved by method [save].
----------------------------------------------------------------------
-- FIXME there are more fields than that to restore
function lexer:restore (s) self.i=s[1]; self.peeked=s[2] end
----------------------------------------------------------------------
-- Resynchronize: cancel any token in self.peeked, by emptying the
-- list and resetting the indexes
----------------------------------------------------------------------
function lexer:sync()
local p1 = self.peeked[1]
if p1 then
li = p1.lineinfo.first
self.line, self.i = li[1], li[3]
self.column_offset = self.i - li[2]
self.peeked = { }
self.attached_comments = p1.lineinfo.first.comments or { }
end
end
----------------------------------------------------------------------
-- Take the source and offset of an old lexer.
----------------------------------------------------------------------
function lexer:takeover(old)
self:sync()
self.line, self.column_offset, self.i, self.src, self.attached_comments =
old.line, old.column_offset, old.i, old.src, old.attached_comments
return self
end
-- function lexer:lineinfo()
-- if self.peeked[1] then return self.peeked[1].lineinfo.first
-- else return { self.line, self.i-self.column_offset, self.i } end
-- end
----------------------------------------------------------------------
-- Return the current position in the sources. This position is between
-- two tokens, and can be within a space / comment area, and therefore
-- have a non-null width. :lineinfo_left() returns the beginning of the
-- separation area, :lineinfo_right() returns the end of that area.
--
-- ____ last consummed token ____ first unconsummed token
-- / /
-- XXXXX <spaces and comments> YYYYY
-- \____ \____
-- :lineinfo_left() :lineinfo_right()
----------------------------------------------------------------------
function lexer:lineinfo_right()
return self:peek(1).lineinfo.first
end
function lexer:lineinfo_left()
return self.lineinfo_last
end
----------------------------------------------------------------------
-- Create a new lexstream.
----------------------------------------------------------------------
function lexer:newstream (src_or_stream, name)
name = name or "?"
if type(src_or_stream)=='table' then -- it's a stream
return setmetatable ({ }, self) :takeover (src_or_stream)
elseif type(src_or_stream)=='string' then -- it's a source string
local src = src_or_stream
local stream = {
src_name = name; -- Name of the file
src = src; -- The source, as a single string
peeked = { }; -- Already peeked, but not discarded yet, tokens
i = 1; -- Character offset in src
line = 1; -- Current line number
column_offset = 0; -- distance from beginning of file to last '\n'
attached_comments = { },-- comments accumulator
lineinfo_last = { 1, 1, 1, name }
}
setmetatable (stream, self)
-- skip initial sharp-bang for unix scripts
-- FIXME: redundant with mlp.chunk()
if src and src :match "^#" then stream.i = src :find "\n" + 1 end
return stream
else
assert(false, ":newstream() takes a source string or a stream, not a "..
type(src_or_stream))
end
end
----------------------------------------------------------------------
-- if there's no ... args, return the token a (whose truth value is
-- true) if it's a `Keyword{ }, or nil. If there are ... args, they
-- have to be strings. if the token a is a keyword, and it's content
-- is one of the ... args, then returns it (it's truth value is
-- true). If no a keyword or not in ..., return nil.
----------------------------------------------------------------------
function lexer:is_keyword (a, ...)
if not a or a.tag ~= "Keyword" then return false end
local words = {...}
if #words == 0 then return a[1] end
for _, w in ipairs (words) do
if w == a[1] then return w end
end
return false
end
----------------------------------------------------------------------
-- Cause an error if the next token isn't a keyword whose content
-- is listed among ... args (which have to be strings).
----------------------------------------------------------------------
function lexer:check (...)
local words = {...}
local a = self:next()
local function err ()
error ("Got " .. tostring (a) ..
", expected one of these keywords : '" ..
_G.table.concat (words,"', '") .. "'") end
if not a or a.tag ~= "Keyword" then err () end
if #words == 0 then return a[1] end
for _, w in ipairs (words) do
if w == a[1] then return w end
end
err ()
end
----------------------------------------------------------------------
--
----------------------------------------------------------------------
function lexer:clone()
local clone = {
alpha = table.deep_copy(self.alpha),
sym = table.deep_copy(self.sym) }
setmetatable(clone, self)
clone.__index = clone
return clone
end

View File

@@ -0,0 +1,440 @@
----------------------------------------------------------------------
--
-- WARNING! You're entering a hackish area, proceed at your own risks!
--
-- This code results from the borrowing, then ruthless abuse, of
-- Yueliang's implementation of Lua 5.0 compiler. I claim
-- responsibility for all of the ugly, dirty stuff that you might spot
-- in it.
--
-- Eventually, this code will be rewritten, either in Lua or more
-- probably in C. Meanwhile, if you're interested into digging
-- metalua's sources, this is not the best part to invest your time
-- on.
--
-- End of warning.
--
----------------------------------------------------------------------
--[[--------------------------------------------------------------------
$Id$
lopcodes.lua
Lua 5 virtual machine opcodes in Lua
This file is part of Yueliang.
Copyright (c) 2005 Kein-Hong Man <khman@users.sf.net>
The COPYRIGHT file describes the conditions
under which this software may be distributed.
See the ChangeLog for more information.
------------------------------------------------------------------------
[FF] Slightly modified, mainly to produce Lua 5.1 bytecode.
----------------------------------------------------------------------]]
--[[--------------------------------------------------------------------
-- Notes:
-- * an Instruction is a table with OP, A, B, C, Bx elements; this
-- should allow instruction handling to work with doubles and ints
-- * Added:
-- luaP:Instruction(i): convert field elements to a 4-char string
-- luaP:DecodeInst(x): convert 4-char string into field elements
-- * WARNING luaP:Instruction outputs instructions encoded in little-
-- endian form and field size and positions are hard-coded
----------------------------------------------------------------------]]
module("bytecode", package.seeall)
local function debugf() end
luaP = { }
--[[
===========================================================================
We assume that instructions are unsigned numbers.
All instructions have an opcode in the first 6 bits.
Instructions can have the following fields:
'A' : 8 bits
'B' : 9 bits
'C' : 9 bits
'Bx' : 18 bits ('B' and 'C' together)
'sBx' : signed Bx
A signed argument is represented in excess K; that is, the number
value is the unsigned value minus K. K is exactly the maximum value
for that argument (so that -max is represented by 0, and +max is
represented by 2*max), which is half the maximum for the corresponding
unsigned argument.
===========================================================================
--]]
luaP.OpMode = {"iABC", "iABx", "iAsBx"} -- basic instruction format
------------------------------------------------------------------------
-- size and position of opcode arguments.
-- * WARNING size and position is hard-coded elsewhere in this script
------------------------------------------------------------------------
luaP.SIZE_C = 9
luaP.SIZE_B = 9
luaP.SIZE_Bx = luaP.SIZE_C + luaP.SIZE_B
luaP.SIZE_A = 8
luaP.SIZE_OP = 6
luaP.POS_C = luaP.SIZE_OP
luaP.POS_B = luaP.POS_C + luaP.SIZE_C
luaP.POS_Bx = luaP.POS_C
luaP.POS_A = luaP.POS_B + luaP.SIZE_B
--FF from 5.1
luaP.BITRK = 2^(luaP.SIZE_B - 1)
function luaP:ISK(x) return x >= self.BITRK end
luaP.MAXINDEXRK = luaP.BITRK - 1
function luaP:RKASK(x)
if x < self.BITRK then return x+self.BITRK else return x end
end
------------------------------------------------------------------------
-- limits for opcode arguments.
-- we use (signed) int to manipulate most arguments,
-- so they must fit in BITS_INT-1 bits (-1 for sign)
------------------------------------------------------------------------
-- removed "#if SIZE_Bx < BITS_INT-1" test, assume this script is
-- running on a Lua VM with double or int as LUA_NUMBER
luaP.MAXARG_Bx = math.ldexp(1, luaP.SIZE_Bx) - 1
luaP.MAXARG_sBx = math.floor(luaP.MAXARG_Bx / 2) -- 'sBx' is signed
luaP.MAXARG_A = math.ldexp(1, luaP.SIZE_A) - 1
luaP.MAXARG_B = math.ldexp(1, luaP.SIZE_B) - 1
luaP.MAXARG_C = math.ldexp(1, luaP.SIZE_C) - 1
-- creates a mask with 'n' 1 bits at position 'p'
-- MASK1(n,p) deleted
-- creates a mask with 'n' 0 bits at position 'p'
-- MASK0(n,p) deleted
--[[--------------------------------------------------------------------
Visual representation for reference:
31 | | | 0 bit position
+-----+-----+-----+----------+
| B | C | A | Opcode | iABC format
+-----+-----+-----+----------+
- 9 - 9 - 8 - 6 - field sizes
+-----+-----+-----+----------+
| [s]Bx | A | Opcode | iABx | iAsBx format
+-----+-----+-----+----------+
----------------------------------------------------------------------]]
------------------------------------------------------------------------
-- the following macros help to manipulate instructions
-- * changed to a table object representation, very clean compared to
-- the [nightmare] alternatives of using a number or a string
------------------------------------------------------------------------
-- these accept or return opcodes in the form of string names
function luaP:GET_OPCODE(i) return self.ROpCode[i.OP] end
function luaP:SET_OPCODE(i, o) i.OP = self.OpCode[o] end
function luaP:GETARG_A(i) return i.A end
function luaP:SETARG_A(i, u) i.A = u end
function luaP:GETARG_B(i) return i.B end
function luaP:SETARG_B(i, b) i.B = b end
function luaP:GETARG_C(i) return i.C end
function luaP:SETARG_C(i, b) i.C = b end
function luaP:GETARG_Bx(i) return i.Bx end
function luaP:SETARG_Bx(i, b) i.Bx = b end
function luaP:GETARG_sBx(i) return i.Bx - self.MAXARG_sBx end
function luaP:SETARG_sBx(i, b) i.Bx = b + self.MAXARG_sBx end
function luaP:CREATE_ABC(o,a,b,c)
return {OP = self.OpCode[o], A = a, B = b, C = c}
end
function luaP:CREATE_ABx(o,a,bc)
return {OP = self.OpCode[o], A = a, Bx = bc}
end
------------------------------------------------------------------------
-- Bit shuffling stuffs
------------------------------------------------------------------------
if false and pcall (require, 'bit') then
------------------------------------------------------------------------
-- Return a 4-char string little-endian encoded form of an instruction
------------------------------------------------------------------------
function luaP:Instruction(i)
--FIXME
end
else
------------------------------------------------------------------------
-- Version without bit manipulation library.
------------------------------------------------------------------------
local p2 = {1,2,4,8,16,32,64,128,256, 512, 1024, 2048, 4096}
-- keeps [n] bits from [x]
local function keep (x, n) return x % p2[n+1] end
-- shifts bits of [x] [n] places to the right
local function srb (x,n) return math.floor (x / p2[n+1]) end
-- shifts bits of [x] [n] places to the left
local function slb (x,n) return x * p2[n+1] end
------------------------------------------------------------------------
-- Return a 4-char string little-endian encoded form of an instruction
------------------------------------------------------------------------
function luaP:Instruction(i)
-- printf("Instr->string: %s %s", self.opnames[i.OP], table.tostring(i))
local c0, c1, c2, c3
-- change to OP/A/B/C format if needed
if i.Bx then i.C = keep (i.Bx, 9); i.B = srb (i.Bx, 9) end
-- c0 = 6B from opcode + 2LSB from A (flushed to MSB)
c0 = i.OP + slb (keep (i.A, 2), 6)
-- c1 = 6MSB from A + 2LSB from C (flushed to MSB)
c1 = srb (i.A, 2) + slb (keep (i.C, 2), 6)
-- c2 = 7MSB from C + 1LSB from B (flushed to MSB)
c2 = srb (i.C, 2) + slb (keep (i.B, 1), 7)
-- c3 = 8MSB from B
c3 = srb (i.B, 1)
--printf ("Instruction: %s %s", self.opnames[i.OP], tostringv (i))
--printf ("Bin encoding: %x %x %x %x", c0, c1, c2, c3)
return string.char(c0, c1, c2, c3)
end
end
------------------------------------------------------------------------
-- decodes a 4-char little-endian string into an instruction struct
------------------------------------------------------------------------
function luaP:DecodeInst(x)
error "Not implemented"
end
------------------------------------------------------------------------
-- invalid register that fits in 8 bits
------------------------------------------------------------------------
luaP.NO_REG = luaP.MAXARG_A
------------------------------------------------------------------------
-- R(x) - register
-- Kst(x) - constant (in constant table)
-- RK(x) == if x < MAXSTACK then R(x) else Kst(x-MAXSTACK)
------------------------------------------------------------------------
------------------------------------------------------------------------
-- grep "ORDER OP" if you change these enums
------------------------------------------------------------------------
--[[--------------------------------------------------------------------
Lua virtual machine opcodes (enum OpCode):
------------------------------------------------------------------------
name args description
------------------------------------------------------------------------
OP_MOVE A B R(A) := R(B)
OP_LOADK A Bx R(A) := Kst(Bx)
OP_LOADBOOL A B C R(A) := (Bool)B; if (C) PC++
OP_LOADNIL A B R(A) := ... := R(B) := nil
OP_GETUPVAL A B R(A) := UpValue[B]
OP_GETGLOBAL A Bx R(A) := Gbl[Kst(Bx)]
OP_GETTABLE A B C R(A) := R(B)[RK(C)]
OP_SETGLOBAL A Bx Gbl[Kst(Bx)] := R(A)
OP_SETUPVAL A B UpValue[B] := R(A)
OP_SETTABLE A B C R(A)[RK(B)] := RK(C)
OP_NEWTABLE A B C R(A) := {} (size = B,C)
OP_SELF A B C R(A+1) := R(B); R(A) := R(B)[RK(C)]
OP_ADD A B C R(A) := RK(B) + RK(C)
OP_SUB A B C R(A) := RK(B) - RK(C)
OP_MUL A B C R(A) := RK(B) * RK(C)
OP_DIV A B C R(A) := RK(B) / RK(C)
OP_POW A B C R(A) := RK(B) ^ RK(C)
OP_UNM A B R(A) := -R(B)
OP_NOT A B R(A) := not R(B)
OP_CONCAT A B C R(A) := R(B).. ... ..R(C)
OP_JMP sBx PC += sBx
OP_EQ A B C if ((RK(B) == RK(C)) ~= A) then pc++
OP_LT A B C if ((RK(B) < RK(C)) ~= A) then pc++
OP_LE A B C if ((RK(B) <= RK(C)) ~= A) then pc++
OP_TEST A B C if (R(B) <=> C) then R(A) := R(B) else pc++
OP_CALL A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1))
OP_TAILCALL A B C return R(A)(R(A+1), ... ,R(A+B-1))
OP_RETURN A B return R(A), ... ,R(A+B-2) (see note)
OP_FORLOOP A sBx R(A)+=R(A+2); if R(A) <?= R(A+1) then PC+= sBx
OP_TFORLOOP A C R(A+2), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2));
if R(A+2) ~= nil then pc++
OP_TFORPREP A sBx if type(R(A)) == table then R(A+1):=R(A), R(A):=next;
PC += sBx
OP_SETLIST A Bx R(A)[Bx-Bx%FPF+i] := R(A+i), 1 <= i <= Bx%FPF+1
OP_SETLISTO A Bx (see note)
OP_CLOSE A close all variables in the stack up to (>=) R(A)
OP_CLOSURE A Bx R(A) := closure(KPROTO[Bx], R(A), ... ,R(A+n))
----------------------------------------------------------------------]]
luaP.opnames = {} -- opcode names
luaP.OpCode = {} -- lookup name -> number
luaP.ROpCode = {} -- lookup number -> name
local i = 0
for v in string.gfind([[
MOVE -- 0
LOADK
LOADBOOL
LOADNIL
GETUPVAL
GETGLOBAL -- 5
GETTABLE
SETGLOBAL
SETUPVAL
SETTABLE
NEWTABLE -- 10
SELF
ADD
SUB
MUL
DIV -- 15
MOD
POW
UNM
NOT
LEN -- 20
CONCAT
JMP
EQ
LT
LE -- 25
TEST
TESTSET
CALL
TAILCALL
RETURN -- 30
FORLOOP
FORPREP
TFORLOOP
SETLIST
CLOSE -- 35
CLOSURE
VARARG
]], "[%a]+") do
local n = "OP_"..v
luaP.opnames[i] = v
luaP.OpCode[n] = i
luaP.ROpCode[i] = n
i = i + 1
end
luaP.NUM_OPCODES = i
--[[
===========================================================================
Notes:
(1) In OP_CALL, if (B == 0) then B = top. C is the number of returns - 1,
and can be 0: OP_CALL then sets 'top' to last_result+1, so
next open instruction (OP_CALL, OP_RETURN, OP_SETLIST) may use 'top'.
(2) In OP_RETURN, if (B == 0) then return up to 'top'
(3) For comparisons, B specifies what conditions the test should accept.
(4) All 'skips' (pc++) assume that next instruction is a jump
(5) OP_SETLISTO is used when the last item in a table constructor is a
function, so the number of elements set is up to top of stack
===========================================================================
--]]
------------------------------------------------------------------------
-- masks for instruction properties
------------------------------------------------------------------------
-- was enum OpModeMask:
luaP.OpModeBreg = 2 -- B is a register
luaP.OpModeBrk = 3 -- B is a register/constant
luaP.OpModeCrk = 4 -- C is a register/constant
luaP.OpModesetA = 5 -- instruction set register A
luaP.OpModeK = 6 -- Bx is a constant
luaP.OpModeT = 1 -- operator is a test
------------------------------------------------------------------------
-- get opcode mode, e.g. "iABC"
------------------------------------------------------------------------
function luaP:getOpMode(m)
--printv(m)
--printv(self.OpCode[m])
--printv(self.opmodes [self.OpCode[m]+1])
return self.OpMode[tonumber(string.sub(self.opmodes[self.OpCode[m] + 1], 7, 7))]
end
------------------------------------------------------------------------
-- test an instruction property flag
-- * b is a string, e.g. "OpModeBreg"
------------------------------------------------------------------------
function luaP:testOpMode(m, b)
return (string.sub(self.opmodes[self.OpCode[m] + 1], self[b], self[b]) == "1")
end
-- number of list items to accumulate before a SETLIST instruction
-- (must be a power of 2)
-- * used in lparser, lvm, ldebug, ltests
luaP.LFIELDS_PER_FLUSH = 50 --FF updated to match 5.1
-- luaP_opnames[] is set above, as the luaP.opnames table
-- opmode(t,b,bk,ck,sa,k,m) deleted
--[[--------------------------------------------------------------------
Legend for luaP:opmodes:
1 T -> T (is a test?)
2 B -> B is a register
3 b -> B is an RK register/constant combination
4 C -> C is an RK register/constant combination
5 A -> register A is set by the opcode
6 K -> Bx is a constant
7 m -> 1 if iABC layout,
2 if iABx layout,
3 if iAsBx layout
----------------------------------------------------------------------]]
luaP.opmodes = {
-- TBbCAKm opcode
"0100101", -- OP_MOVE 0
"0000112", -- OP_LOADK
"0000101", -- OP_LOADBOOL
"0100101", -- OP_LOADNIL
"0000101", -- OP_GETUPVAL
"0000112", -- OP_GETGLOBAL 5
"0101101", -- OP_GETTABLE
"0000012", -- OP_SETGLOBAL
"0000001", -- OP_SETUPVAL
"0011001", -- OP_SETTABLE
"0000101", -- OP_NEWTABLE 10
"0101101", -- OP_SELF
"0011101", -- OP_ADD
"0011101", -- OP_SUB
"0011101", -- OP_MUL
"0011101", -- OP_DIV 15
"0011101", -- OP_MOD
"0011101", -- OP_POW
"0100101", -- OP_UNM
"0100101", -- OP_NOT
"0100101", -- OP_LEN 20
"0101101", -- OP_CONCAT
"0000003", -- OP_JMP
"1011001", -- OP_EQ
"1011001", -- OP_LT
"1011001", -- OP_LE 25
"1000101", -- OP_TEST
"1100101", -- OP_TESTSET
"0000001", -- OP_CALL
"0000001", -- OP_TAILCALL
"0000001", -- OP_RETURN 30
"0000003", -- OP_FORLOOP
"0000103", -- OP_FORPREP
"1000101", -- OP_TFORLOOP
"0000001", -- OP_SETLIST
"0000001", -- OP_CLOSE 35
"0000102", -- OP_CLOSURE
"0000101" -- OP_VARARG
}

View File

@@ -0,0 +1,60 @@
-- construct proper path to load metalua modules to build
-- an abstract syntax tree (AST)
local file = debug.getinfo(1, "S").source
if string.find(file, "@") == 1 then file = string.sub(file, 2) end
package.path = package.path .. ';' .. string.gsub(file, "metalua%.lua$", "?.lua")
-- these modules are sufficient to build an AST from a source file/string
require "lexer"
require "gg"
require "mlp_lexer"
require "mlp_misc"
require "mlp_table"
require "mlp_meta"
require "mlp_expr"
require "mlp_stat"
-- these modules are needed to convert an AST into bytecode to execute
require "lcode"
require "ldump"
require "lopcodes"
require "compile"
-- this is the compiler module that builds bytecode from an AST
local mlc = { }
function mlc.function_of_ast (ast)
local proto = bytecode.metalua_compile(ast)
local dump = bytecode.dump_string(proto)
local func = string.undump(dump)
return func
end
function mlc.ast_of_luastring (src, file)
local lx = mlp.lexer:newstream(src, file or "(string)")
local ast = mlp.chunk(lx)
return ast
end
function mlc.function_of_luastring (src, file)
local ast = mlc.ast_of_luastring(src, file)
local func = mlc.function_of_ast(ast)
return func
end
function mlc.function_of_luafile (name)
local f = io.open(name, 'r')
local src = f:read('*a')
f:close()
return mlc.function_of_luastring(src, "@"..name)
end
_G.mlc = mlc
--[[
-- Can be used with the following code:
require "metalua"
local ast = mlc.ast_of_luastring(src)
local f = mlc.function_of_ast(ast)
f()
]]

View File

@@ -0,0 +1,104 @@
----------------------------------------------------------------------
----------------------------------------------------------------------
--
-- Base library extension
--
----------------------------------------------------------------------
----------------------------------------------------------------------
if not metalua then rawset(getfenv(), 'metalua', { }) end
metalua.version = "v-0.5"
if not rawpairs then
rawpairs, rawipairs, rawtype = pairs, ipairs, type
end
function pairs(x)
assert(type(x)=='table', 'pairs() expects a table')
local mt = getmetatable(x)
if mt then
local mtp = mt.__pairs
if mtp then return mtp(x) end
end
return rawpairs(x)
end
function ipairs(x)
assert(type(x)=='table', 'ipairs() expects a table')
local mt = getmetatable(x)
if mt then
local mti = mt.__ipairs
if mti then return mti(x) end
end
return rawipairs(x)
end
--[[
function type(x)
local mt = getmetatable(x)
if mt then
local mtt = mt.__type
if mtt then return mtt end
end
return rawtype(x)
end
]]
function min (a, ...)
for n in values{...} do if n<a then a=n end end
return a
end
function max (a, ...)
for n in values{...} do if n>a then a=n end end
return a
end
function o (...)
local args = {...}
local function g (...)
local result = {...}
for i=#args, 1, -1 do result = {args[i](unpack(result))} end
return unpack (result)
end
return g
end
function id (...) return ... end
function const (k) return function () return k end end
function printf(...) return print(string.format(...)) end
function eprintf(...)
io.stderr:write(string.format(...).."\n")
end
function ivalues (x)
assert(type(x)=='table', 'ivalues() expects a table')
local i = 1
local function iterator ()
local r = x[i]; i=i+1; return r
end
return iterator
end
function values (x)
assert(type(x)=='table', 'values() expects a table')
local function iterator (state)
local it
state.content, it = next(state.list, state.content)
return it
end
return iterator, { list = x }
end
function keys (x)
assert(type(x)=='table', 'keys() expects a table')
local function iterator (state)
local it = next(state.list, state.content)
state.content = it
return it
end
return iterator, { list = x }
end

View File

@@ -0,0 +1,3 @@
require 'metalua.base'
require 'metalua.table2'
require 'metalua.string2'

View File

@@ -0,0 +1,43 @@
----------------------------------------------------------------------
----------------------------------------------------------------------
--
-- String module extension
--
----------------------------------------------------------------------
----------------------------------------------------------------------
-- Courtesy of lua-users.org
function string.split(str, pat)
local t = {}
local fpat = "(.-)" .. pat
local last_end = 1
local s, e, cap = string.find(str, fpat, 1)
while s do
if s ~= 1 or cap ~= "" then
table.insert(t,cap)
end
last_end = e+1
s, e, cap = string.find(str, fpat, last_end)
end
if last_end <= string.len(str) then
cap = string.sub(str, last_end)
table.insert(t, cap)
end
return t
end
-- "match" is regularly used as a keyword for pattern matching,
-- so here is an always available substitute.
string.strmatch = string["match"]
-- change a compiled string into a function
function string.undump(str)
if str:strmatch '^\027LuaQ' or str:strmatch '^#![^\n]+\n\027LuaQ' then
local f = (lua_loadstring or loadstring)(str)
return f
else
error "Not a chunk dump"
end
end
return string

View File

@@ -0,0 +1,380 @@
---------------------------------------------------------------------
----------------------------------------------------------------------
--
-- Table module extension
--
----------------------------------------------------------------------
----------------------------------------------------------------------
-- todo: table.scan (scan1?) fold1? flip?
function table.transpose(t)
local tt = { }
for a, b in pairs(t) do tt[b] = a end
return tt
end
function table.iforeach(f, ...)
-- assert (type (f) == "function") [wouldn't allow metamethod __call]
local nargs = select("#", ...)
if nargs==1 then -- Quick iforeach (most common case), just one table arg
local t = ...
assert (type (t) == "table")
for i = 1, #t do
local result = f (t[i])
-- If the function returns non-false, stop iteration
if result then return result end
end
else -- advanced case: boundaries and/or multiple tables
-- fargs: arguments fot a single call to f
-- first, last: indexes of the first & last elements mapped in each table
-- arg1: index of the first table in args
-- 1 - find boundaries if any
local args, fargs, first, last, arg1 = {...}, { }
if type(args[1]) ~= "number" then first, arg1 = 1, 1 -- no boundary
elseif type(args[2]) ~= "number" then first, last, arg1 = 1, args[1], 2
else first, last, arg1 = args[1], args[2], 3 end
assert (nargs >= arg1) -- at least one table
-- 2 - determine upper boundary if not given
if not last then for i = arg1, nargs do
assert (type (args[i]) == "table")
last = max (#args[i], last)
end end
-- 3 - remove non-table arguments from args, adjust nargs
if arg1>1 then args = { select(arg1, unpack(args)) }; nargs = #args end
-- 4 - perform the iteration
for i = first, last do
for j = 1, nargs do fargs[j] = args[j][i] end -- build args list
local result = f (unpack (fargs)) -- here is the call
-- If the function returns non-false, stop iteration
if result then return result end
end
end
end
function table.imap (f, ...)
local result, idx = { }, 1
local function g(...) result[idx] = f(...); idx=idx+1 end
table.iforeach(g, ...)
return result
end
function table.ifold (f, acc, ...)
local function g(...) acc = f (acc,...) end
table.iforeach (g, ...)
return acc
end
-- function table.ifold1 (f, ...)
-- return table.ifold (f, acc, 2, false, ...)
-- end
function table.izip(...)
local function g(...) return {...} end
return table.imap(g, ...)
end
function table.ifilter(f, t)
local yes, no = { }, { }
for i=1,#t do table.insert (f(t[i]) and yes or no, t[i]) end
return yes, no
end
function table.icat(...)
local result = { }
for t in values {...} do
for x in values (t) do
table.insert (result, x)
end
end
return result
end
function table.iflatten (x) return table.icat (unpack (x)) end
function table.irev (t)
local result, nt = { }, #t
for i=0, nt-1 do result[nt-i] = t[i+1] end
return result
end
function table.isub (t, ...)
local ti, u = table.insert, { }
local args, nargs = {...}, select("#", ...)
for i=1, nargs/2 do
local a, b = args[2*i-1], args[2*i]
for i=a, b, a<=b and 1 or -1 do ti(u, t[i]) end
end
return u
end
function table.iall (f, ...)
local result = true
local function g(...) return not f(...) end
return not table.iforeach(g, ...)
--return result
end
function table.iany (f, ...)
local function g(...) return not f(...) end
return not table.iall(g, ...)
end
function table.shallow_copy(x)
local y={ }
for k, v in pairs(x) do y[k]=v end
return y
end
-- Warning, this is implementation dependent: it relies on
-- the fact the [next()] enumerates the array-part before the hash-part.
function table.cat(...)
local y={ }
for x in values{...} do
-- cat array-part
for _, v in ipairs(x) do table.insert(y,v) end
-- cat hash-part
local lx, k = #x
if lx>0 then k=next(x,lx) else k=next(x) end
while k do y[k]=x[k]; k=next(x,k) end
end
return y
end
function table.deep_copy(x)
local tracker = { }
local function aux (x)
if type(x) == "table" then
local y=tracker[x]
if y then return y end
y = { }; tracker[x] = y
setmetatable (y, getmetatable (x))
for k,v in pairs(x) do y[aux(k)] = aux(v) end
return y
else return x end
end
return aux(x)
end
function table.override(dst, src)
for k, v in pairs(src) do dst[k] = v end
for i = #src+1, #dst do dst[i] = nil end
return dst
end
function table.range(a,b,c)
if not b then assert(not(c)); b=a; a=1
elseif not c then c = (b>=a) and 1 or -1 end
local result = { }
for i=a, b, c do table.insert(result, i) end
return result
end
-- FIXME: new_indent seems to be always nil?!
-- FIXME: accumulator function should be configurable,
-- so that print() doesn't need to bufferize the whole string
-- before starting to print.
function table.tostring(t, ...)
local PRINT_HASH, HANDLE_TAG, FIX_INDENT, LINE_MAX, INITIAL_INDENT = true, true
for _, x in ipairs {...} do
if type(x) == "number" then
if not LINE_MAX then LINE_MAX = x
else INITIAL_INDENT = x end
elseif x=="nohash" then PRINT_HASH = false
elseif x=="notag" then HANDLE_TAG = false
else
local n = string['match'](x, "^indent%s*(%d*)$")
if n then FIX_INDENT = tonumber(n) or 3 end
end
end
LINE_MAX = LINE_MAX or math.huge
INITIAL_INDENT = INITIAL_INDENT or 1
local current_offset = 0 -- indentation level
local xlen_cache = { } -- cached results for xlen()
local acc_list = { } -- Generated bits of string
local function acc(...) -- Accumulate a bit of string
local x = table.concat{...}
current_offset = current_offset + #x
table.insert(acc_list, x)
end
local function valid_id(x)
-- FIXME: we should also reject keywords; but the list of
-- current keywords is not fixed in metalua...
return type(x) == "string"
and string['match'](x, "^[a-zA-Z_][a-zA-Z0-9_]*$")
end
-- Compute the number of chars it would require to display the table
-- on a single line. Helps to decide whether some carriage returns are
-- required. Since the size of each sub-table is required many times,
-- it's cached in [xlen_cache].
local xlen_type = { }
local function xlen(x, nested)
nested = nested or { }
if x==nil then return #"nil" end
--if nested[x] then return #tostring(x) end -- already done in table
local len = xlen_cache[x]
if len then return len end
local f = xlen_type[type(x)]
if not f then return #tostring(x) end
len = f (x, nested)
xlen_cache[x] = len
return len
end
-- optim: no need to compute lengths if I'm not going to use them
-- anyway.
if LINE_MAX == math.huge then xlen = function() return 0 end end
xlen_type["nil"] = function () return 3 end
function xlen_type.number (x) return #tostring(x) end
function xlen_type.boolean (x) return x and 4 or 5 end
function xlen_type.string (x) return #string.format("%q",x) end
function xlen_type.table (adt, nested)
-- Circular references detection
if nested [adt] then return #tostring(adt) end
nested [adt] = true
local has_tag = HANDLE_TAG and valid_id(adt.tag)
local alen = #adt
local has_arr = alen>0
local has_hash = false
local x = 0
if PRINT_HASH then
-- first pass: count hash-part
for k, v in pairs(adt) do
if k=="tag" and has_tag then
-- this is the tag -> do nothing!
elseif type(k)=="number" and k<=alen and math.fmod(k,1)==0 then
-- array-part pair -> do nothing!
else
has_hash = true
if valid_id(k) then x=x+#k
else x = x + xlen (k, nested) + 2 end -- count surrounding brackets
x = x + xlen (v, nested) + 5 -- count " = " and ", "
end
end
end
for i = 1, alen do x = x + xlen (adt[i], nested) + 2 end -- count ", "
nested[adt] = false -- No more nested calls
if not (has_tag or has_arr or has_hash) then return 3 end
if has_tag then x=x+#adt.tag+1 end
if not (has_arr or has_hash) then return x end
if not has_hash and alen==1 and type(adt[1])~="table" then
return x-2 -- substract extraneous ", "
end
return x+2 -- count "{ " and " }", substract extraneous ", "
end
-- Recursively print a (sub) table at given indentation level.
-- [newline] indicates whether newlines should be inserted.
local function rec (adt, nested, indent)
if not FIX_INDENT then indent = current_offset end
local function acc_newline()
acc ("\n"); acc (string.rep (" ", indent))
current_offset = indent
end
local x = { }
x["nil"] = function() acc "nil" end
function x.number() acc (tostring (adt)) end
--function x.string() acc (string.format ("%q", adt)) end
function x.string() acc ((string.format ("%q", adt):gsub("\\\n", "\\n"))) end
function x.boolean() acc (adt and "true" or "false") end
function x.table()
if nested[adt] then acc(tostring(adt)); return end
nested[adt] = true
local has_tag = HANDLE_TAG and valid_id(adt.tag)
local alen = #adt
local has_arr = alen>0
local has_hash = false
if has_tag then acc("`"); acc(adt.tag) end
-- First pass: handle hash-part
if PRINT_HASH then
for k, v in pairs(adt) do
-- pass if the key belongs to the array-part or is the "tag" field
if not (k=="tag" and HANDLE_TAG) and
not (type(k)=="number" and k<=alen and math.fmod(k,1)==0) then
-- Is it the first time we parse a hash pair?
if not has_hash then
acc "{ "
if not FIX_INDENT then indent = current_offset end
else acc ", " end
-- Determine whether a newline is required
local is_id, expected_len = valid_id(k)
if is_id then expected_len = #k + xlen (v, nested) + #" = , "
else expected_len = xlen (k, nested) +
xlen (v, nested) + #"[] = , " end
if has_hash and expected_len + current_offset > LINE_MAX
then acc_newline() end
-- Print the key
if is_id then acc(k); acc " = "
else acc "["; rec (k, nested, indent+(FIX_INDENT or 0)); acc "] = " end
-- Print the value
rec (v, nested, indent+(FIX_INDENT or 0))
has_hash = true
end
end
end
-- Now we know whether there's a hash-part, an array-part, and a tag.
-- Tag and hash-part are already printed if they're present.
if not has_tag and not has_hash and not has_arr then acc "{ }";
elseif has_tag and not has_hash and not has_arr then -- nothing, tag already in acc
else
assert (has_hash or has_arr)
local no_brace = false
if has_hash and has_arr then acc ", "
elseif has_tag and not has_hash and alen==1 and type(adt[1])~="table" then
-- No brace required; don't print "{", remember not to print "}"
acc (" "); rec (adt[1], nested, indent+(FIX_INDENT or 0))
no_brace = true
elseif not has_hash then
-- Braces required, but not opened by hash-part handler yet
acc "{ "
if not FIX_INDENT then indent = current_offset end
end
-- 2nd pass: array-part
if not no_brace and has_arr then
rec (adt[1], nested, indent+(FIX_INDENT or 0))
for i=2, alen do
acc ", ";
if current_offset + xlen (adt[i], { }) > LINE_MAX
then acc_newline() end
rec (adt[i], nested, indent+(FIX_INDENT or 0))
end
end
if not no_brace then acc " }" end
end
nested[adt] = false -- No more nested calls
end
local y = x[type(adt)]
if y then y() else acc(tostring(adt)) end
end
--printf("INITIAL_INDENT = %i", INITIAL_INDENT)
current_offset = INITIAL_INDENT or 0
rec(t, { }, 0)
return table.concat (acc_list)
end
function table.print(...) return print(table.tostring(...)) end
return table

View File

@@ -0,0 +1,213 @@
----------------------------------------------------------------------
-- Metalua: $Id: mlp_expr.lua,v 1.7 2006/11/15 09:07:50 fab13n Exp $
--
-- Summary: metalua parser, expression parser. This is part of the
-- definition of module [mlp].
--
----------------------------------------------------------------------
--
-- Copyright (c) 2006, Fabien Fleutot <metalua@gmail.com>.
--
-- This software is released under the MIT Licence, see licence.txt
-- for details.
--
----------------------------------------------------------------------
-- History:
-- $Log: mlp_expr.lua,v $
-- Revision 1.7 2006/11/15 09:07:50 fab13n
-- debugged meta operators.
-- Added command line options handling.
--
-- Revision 1.6 2006/11/10 02:11:17 fab13n
-- compiler faithfulness to 5.1 improved
-- gg.expr extended
-- mlp.expr refactored
--
-- Revision 1.5 2006/11/09 09:39:57 fab13n
-- some cleanup
--
-- Revision 1.4 2006/11/07 21:29:02 fab13n
-- improved quasi-quoting
--
-- Revision 1.3 2006/11/07 04:38:00 fab13n
-- first bootstrapping version.
--
-- Revision 1.2 2006/11/05 15:08:34 fab13n
-- updated code generation, to be compliant with 5.1
--
----------------------------------------------------------------------
--------------------------------------------------------------------------------
--
-- Exported API:
-- * [mlp.expr()]
-- * [mlp.expr_list()]
-- * [mlp.func_val()]
--
--------------------------------------------------------------------------------
--require "gg"
--require "mlp_misc"
--require "mlp_table"
--require "mlp_meta"
--------------------------------------------------------------------------------
-- These function wrappers (eta-expansions ctually) are just here to break
-- some circular dependencies between mlp_xxx.lua files.
--------------------------------------------------------------------------------
local function _expr (lx) return mlp.expr (lx) end
local function _table_content (lx) return mlp.table_content (lx) end
local function block (lx) return mlp.block (lx) end
local function stat (lx) return mlp.stat (lx) end
module ("mlp", package.seeall)
--------------------------------------------------------------------------------
-- Non-empty expression list. Actually, this isn't used here, but that's
-- handy to give to users.
--------------------------------------------------------------------------------
expr_list = gg.list{ _expr, separators = "," }
--------------------------------------------------------------------------------
-- Helpers for function applications / method applications
--------------------------------------------------------------------------------
func_args_content = gg.list {
name = "function arguments",
_expr, separators = ",", terminators = ")" }
-- Used to parse methods
method_args = gg.multisequence{
name = "function argument(s)",
{ "{", table_content, "}" },
{ "(", func_args_content, ")", builder = fget(1) },
{ "+{", quote_content, "}" },
function(lx) local r = opt_string(lx); return r and {r} or { } end }
--------------------------------------------------------------------------------
-- [func_val] parses a function, from opening parameters parenthese to
-- "end" keyword included. Used for anonymous functions as well as
-- function declaration statements (both local and global).
--
-- It's wrapped in a [_func_val] eta expansion, so that when expr
-- parser uses the latter, they will notice updates of [func_val]
-- definitions.
--------------------------------------------------------------------------------
func_params_content = gg.list{ name="function parameters",
gg.multisequence{ { "...", builder = "Dots" }, id },
separators = ",", terminators = {")", "|"} }
local _func_params_content = function (lx) return func_params_content(lx) end
func_val = gg.sequence { name="function body",
"(", func_params_content, ")", block, "end", builder = "Function" }
local _func_val = function (lx) return func_val(lx) end
--------------------------------------------------------------------------------
-- Default parser for primary expressions
--------------------------------------------------------------------------------
function id_or_literal (lx)
local a = lx:next()
if a.tag~="Id" and a.tag~="String" and a.tag~="Number" then
local msg
if a.tag=='Eof' then
msg = "End of file reached when an expression was expected"
elseif a.tag=='Keyword' then
msg = "An expression was expected, and `"..a[1]..
"' can't start an expression"
else
msg = "Unexpected expr token " .. _G.table.tostring (a, 'nohash')
end
gg.parse_error (lx, msg)
end
return a
end
--------------------------------------------------------------------------------
-- Builder generator for operators. Wouldn't be worth it if "|x|" notation
-- were allowed, but then lua 5.1 wouldn't compile it
--------------------------------------------------------------------------------
-- opf1 = |op| |_,a| `Op{ op, a }
local function opf1 (op) return
function (_,a) return { tag="Op", op, a } end end
-- opf2 = |op| |a,_,b| `Op{ op, a, b }
local function opf2 (op) return
function (a,_,b) return { tag="Op", op, a, b } end end
-- opf2r = |op| |a,_,b| `Op{ op, b, a } -- (args reversed)
local function opf2r (op) return
function (a,_,b) return { tag="Op", op, b, a } end end
local function op_ne(a, _, b)
-- The first version guarantees to return the same code as Lua,
-- but it relies on the non-standard 'ne' operator, which has been
-- suppressed from the official AST grammar (although still supported
-- in practice by the compiler).
-- return { tag="Op", "ne", a, b }
return { tag="Op", "not", { tag="Op", "eq", a, b, lineinfo= {
first = a.lineinfo.first, last = b.lineinfo.last } } }
end
--------------------------------------------------------------------------------
--
-- complete expression
--
--------------------------------------------------------------------------------
-- FIXME: set line number. In [expr] transformers probably
expr = gg.expr { name = "expression",
primary = gg.multisequence{ name="expr primary",
{ "(", _expr, ")", builder = "Paren" },
{ "function", _func_val, builder = fget(1) },
{ "-{", splice_content, "}", builder = fget(1) },
{ "+{", quote_content, "}", builder = fget(1) },
{ "nil", builder = "Nil" },
{ "true", builder = "True" },
{ "false", builder = "False" },
{ "...", builder = "Dots" },
table,
id_or_literal },
infix = { name="expr infix op",
{ "+", prec = 60, builder = opf2 "add" },
{ "-", prec = 60, builder = opf2 "sub" },
{ "*", prec = 70, builder = opf2 "mul" },
{ "/", prec = 70, builder = opf2 "div" },
{ "%", prec = 70, builder = opf2 "mod" },
{ "^", prec = 90, builder = opf2 "pow", assoc = "right" },
{ "..", prec = 40, builder = opf2 "concat", assoc = "right" },
{ "==", prec = 30, builder = opf2 "eq" },
{ "~=", prec = 30, builder = op_ne },
{ "<", prec = 30, builder = opf2 "lt" },
{ "<=", prec = 30, builder = opf2 "le" },
{ ">", prec = 30, builder = opf2r "lt" },
{ ">=", prec = 30, builder = opf2r "le" },
{ "and",prec = 20, builder = opf2 "and" },
{ "or", prec = 10, builder = opf2 "or" } },
prefix = { name="expr prefix op",
{ "not", prec = 80, builder = opf1 "not" },
{ "#", prec = 80, builder = opf1 "len" },
{ "-", prec = 80, builder = opf1 "unm" } },
suffix = { name="expr suffix op",
{ "[", _expr, "]", builder = function (tab, idx)
return {tag="Index", tab, idx[1]} end},
{ ".", id, builder = function (tab, field)
return {tag="Index", tab, id2string(field[1])} end },
{ "(", func_args_content, ")", builder = function(f, args)
return {tag="Call", f, unpack(args[1])} end },
{ "{", _table_content, "}", builder = function (f, arg)
return {tag="Call", f, arg[1]} end},
{ ":", id, method_args, builder = function (obj, post)
return {tag="Invoke", obj, id2string(post[1]), unpack(post[2])} end},
{ "+{", quote_content, "}", builder = function (f, arg)
return {tag="Call", f, arg[1] } end },
default = { name="opt_string_arg", parse = mlp.opt_string, builder = function(f, arg)
return {tag="Call", f, arg } end } } }

View File

@@ -0,0 +1,89 @@
--------------------------------------------------------------------------------
--
-- Non-Lua syntax extensions
--
--------------------------------------------------------------------------------
module ("mlp", package.seeall)
--------------------------------------------------------------------------------
-- Alebraic Datatypes
--------------------------------------------------------------------------------
local function adt (lx)
local tagval = id (lx) [1]
local tagkey = {tag="Pair", {tag="String", "tag"}, {tag="String", tagval} }
if lx:peek().tag == "String" or lx:peek().tag == "Number" then
return { tag="Table", tagkey, lx:next() }
elseif lx:is_keyword (lx:peek(), "{") then
local x = table (lx)
_G.table.insert (x, 1, tagkey)
return x
else return { tag="Table", tagkey } end
end
expr:add{ "`", adt, builder = fget(1) }
--------------------------------------------------------------------------------
-- Anonymous lambda
--------------------------------------------------------------------------------
local lambda_expr = gg.sequence{
"|", func_params_content, "|", expr,
builder= function (x)
local li = x[2].lineinfo
return { tag="Function", x[1],
{ {tag="Return", x[2], lineinfo=li }, lineinfo=li } }
end }
-- In an earlier version, lambda_expr took an expr_list rather than an expr
-- after the 2nd bar. However, it happened to be much more of a burden than an
-- help, So finally I disabled it. If you want to return several results,
-- use the long syntax.
--------------------------------------------------------------------------------
-- local lambda_expr = gg.sequence{
-- "|", func_params_content, "|", expr_list,
-- builder= function (x)
-- return {tag="Function", x[1], { {tag="Return", unpack(x[2]) } } } end }
expr:add (lambda_expr)
--------------------------------------------------------------------------------
-- Allows to write "a `f` b" instead of "f(a, b)". Taken from Haskell.
-- This is not part of Lua 5.1 syntax, so it's added to the expression
-- afterwards, so that it's easier to disable.
--------------------------------------------------------------------------------
local function expr_in_backquotes (lx) return expr(lx, 35) end
expr.infix:add{ name = "infix function",
"`", expr_in_backquotes, "`", prec = 35, assoc="left",
builder = function(a, op, b) return {tag="Call", op[1], a, b} end }
--------------------------------------------------------------------------------
-- table.override assignment
--------------------------------------------------------------------------------
mlp.lexer:add "<-"
stat.assignments["<-"] = function (a, b)
assert( #a==1 and #b==1, "No multi-args for '<-'")
return { tag="Call", { tag="Index", { tag="Id", "table" },
{ tag="String", "override" } },
a[1], b[1]}
end
--------------------------------------------------------------------------------
-- C-style op+assignments
--------------------------------------------------------------------------------
local function op_assign(kw, op)
local function rhs(a, b)
return { tag="Op", op, a, b }
end
local function f(a,b)
return { tag="Set", a, _G.table.imap(rhs, a, b) }
end
mlp.lexer:add (kw)
mlp.stat.assignments[kw] = f
end
_G.table.iforeach (op_assign,
{"+=", "-=", "*=", "/="},
{"add", "sub", "mul", "div"})

View File

@@ -0,0 +1,32 @@
----------------------------------------------------------------------
-- Metalua: $Id: mll.lua,v 1.3 2006/11/15 09:07:50 fab13n Exp $
--
-- Summary: Source file lexer. ~~Currently only works on strings.
-- Some API refactoring is needed.
--
----------------------------------------------------------------------
--
-- Copyright (c) 2006-2007, Fabien Fleutot <metalua@gmail.com>.
--
-- This software is released under the MIT Licence, see licence.txt
-- for details.
--
----------------------------------------------------------------------
module ("mlp", package.seeall)
require "lexer"
local mlp_lexer = lexer.lexer:clone()
local keywords = {
"and", "break", "do", "else", "elseif",
"end", "false", "for", "function", "if",
"in", "local", "nil", "not", "or", "repeat",
"return", "then", "true", "until", "while",
"...", "..", "==", ">=", "<=", "~=",
"+{", "-{" }
for _,w in pairs(keywords) do mlp_lexer:add(w) end -- PK 6/4/2012
_M.lexer = mlp_lexer

View File

@@ -0,0 +1,118 @@
----------------------------------------------------------------------
-- Metalua: $Id: mlp_meta.lua,v 1.4 2006/11/15 09:07:50 fab13n Exp $
--
-- Summary: Meta-operations: AST quasi-quoting and splicing
--
----------------------------------------------------------------------
--
-- Copyright (c) 2006, Fabien Fleutot <metalua@gmail.com>.
--
-- This software is released under the MIT Licence, see licence.txt
-- for details.
--
----------------------------------------------------------------------
--------------------------------------------------------------------------------
--
-- Exported API:
-- * [mlp.splice_content()]
-- * [mlp.quote_content()]
--
--------------------------------------------------------------------------------
module ("mlp", package.seeall)
--------------------------------------------------------------------------------
-- External splicing: compile an AST into a chunk, load and evaluate
-- that chunk, and replace the chunk by its result (which must also be
-- an AST).
--------------------------------------------------------------------------------
function splice (ast)
local f = mlc.function_of_ast(ast, '=splice')
local result=f()
return result
end
--------------------------------------------------------------------------------
-- Going from an AST to an AST representing that AST
-- the only key being lifted in this version is ["tag"]
--------------------------------------------------------------------------------
function quote (t)
--print("QUOTING:", _G.table.tostring(t, 60))
local cases = { }
function cases.table (t)
local mt = { tag = "Table" }
--_G.table.insert (mt, { tag = "Pair", quote "quote", { tag = "True" } })
if t.tag == "Splice" then
assert (#t==1, "Invalid splice")
local sp = t[1]
return sp
elseif t.tag then
_G.table.insert (mt, { tag = "Pair", quote "tag", quote (t.tag) })
end
for _, v in ipairs (t) do
_G.table.insert (mt, quote(v))
end
return mt
end
function cases.number (t) return { tag = "Number", t, quote = true } end
function cases.string (t) return { tag = "String", t, quote = true } end
return cases [ type (t) ] (t)
end
--------------------------------------------------------------------------------
-- when this variable is false, code inside [-{...}] is compiled and
-- avaluated immediately. When it's true (supposedly when we're
-- parsing data inside a quasiquote), [-{foo}] is replaced by
-- [`Splice{foo}], which will be unpacked by [quote()].
--------------------------------------------------------------------------------
in_a_quote = false
--------------------------------------------------------------------------------
-- Parse the inside of a "-{ ... }"
--------------------------------------------------------------------------------
function splice_content (lx)
local parser_name = "expr"
if lx:is_keyword (lx:peek(2), ":") then
local a = lx:next()
lx:next() -- skip ":"
assert (a.tag=="Id", "Invalid splice parser name")
parser_name = a[1]
end
local ast = mlp[parser_name](lx)
if in_a_quote then
--printf("SPLICE_IN_QUOTE:\n%s", _G.table.tostring(ast, "nohash", 60))
return { tag="Splice", ast }
else
if parser_name == "expr" then ast = { { tag="Return", ast } }
elseif parser_name == "stat" then ast = { ast }
elseif parser_name ~= "block" then
error ("splice content must be an expr, stat or block") end
--printf("EXEC THIS SPLICE:\n%s", _G.table.tostring(ast, "nohash", 60))
return splice (ast)
end
end
--------------------------------------------------------------------------------
-- Parse the inside of a "+{ ... }"
--------------------------------------------------------------------------------
function quote_content (lx)
local parser
if lx:is_keyword (lx:peek(2), ":") then -- +{parser: content }
parser = mlp[id(lx)[1]]
lx:next()
else -- +{ content }
parser = mlp.expr
end
local prev_iq = in_a_quote
in_a_quote = true
--print("IN_A_QUOTE")
local content = parser (lx)
local q_content = quote (content)
in_a_quote = prev_iq
return q_content
end

View File

@@ -0,0 +1,185 @@
----------------------------------------------------------------------
-- Metalua: $Id: mlp_misc.lua,v 1.6 2006/11/15 09:07:50 fab13n Exp $
--
-- Summary: metalua parser, miscellaneous utility functions.
--
----------------------------------------------------------------------
--
-- Copyright (c) 2006, Fabien Fleutot <metalua@gmail.com>.
--
-- This software is released under the MIT Licence, see licence.txt
-- for details.
--
----------------------------------------------------------------------
-- History:
-- $Log: mlp_misc.lua,v $
-- Revision 1.6 2006/11/15 09:07:50 fab13n
-- debugged meta operators.
-- Added command line options handling.
--
-- Revision 1.5 2006/11/10 02:11:17 fab13n
-- compiler faithfulness to 5.1 improved
-- gg.expr extended
-- mlp.expr refactored
--
-- Revision 1.4 2006/11/09 09:39:57 fab13n
-- some cleanup
--
-- Revision 1.3 2006/11/07 04:38:00 fab13n
-- first bootstrapping version.
--
-- Revision 1.2 2006/11/05 15:08:34 fab13n
-- updated code generation, to be compliant with 5.1
--
----------------------------------------------------------------------
--------------------------------------------------------------------------------
--
-- Exported API:
-- * [mlp.fget()]
-- * [mlp.id()]
-- * [mlp.opt_id()]
-- * [mlp.id_list()]
-- * [mlp.gensym()]
-- * [mlp.string()]
-- * [mlp.opt_string()]
-- * [mlp.id2string()]
--
--------------------------------------------------------------------------------
--require "gg"
--require "mll"
module ("mlp", package.seeall)
--------------------------------------------------------------------------------
-- returns a function that takes the [n]th element of a table.
-- if [tag] is provided, then this element is expected to be a
-- table, and this table receives a "tag" field whose value is
-- set to [tag].
--
-- The primary purpose of this is to generate builders for
-- grammar generators. It has little purpose in metalua, as lambda has
-- a lightweight syntax.
--------------------------------------------------------------------------------
function fget (n, tag)
assert (type (n) == "number")
if tag then
assert (type (tag) == "string")
return function (x)
assert (type (x[n]) == "table")
return {tag=tag, unpack(x[n])} end
else
return function (x) return x[n] end
end
end
--------------------------------------------------------------------------------
-- Try to read an identifier (possibly as a splice), or return [false] if no
-- id is found.
--------------------------------------------------------------------------------
function opt_id (lx)
local a = lx:peek();
if lx:is_keyword (a, "-{") then
local v = gg.sequence{ "-{", splice_content, "}" } (lx) [1]
if v.tag ~= "Id" and v.tag ~= "Splice" then
gg.parse_error(lx,"Bad id splice")
end
return v
elseif a.tag == "Id" then return lx:next()
else return false end
end
--------------------------------------------------------------------------------
-- Mandatory reading of an id: causes an error if it can't read one.
--------------------------------------------------------------------------------
function id (lx)
return opt_id (lx) or gg.parse_error(lx,"Identifier expected")
end
--------------------------------------------------------------------------------
-- Common helper function
--------------------------------------------------------------------------------
id_list = gg.list { primary = mlp.id, separators = "," }
--------------------------------------------------------------------------------
-- Symbol generator: [gensym()] returns a guaranteed-to-be-unique identifier.
-- The main purpose is to avoid variable capture in macros.
--
-- If a string is passed as an argument, theis string will be part of the
-- id name (helpful for macro debugging)
--------------------------------------------------------------------------------
local gensymidx = 0
function gensym (arg)
gensymidx = gensymidx + 1
return { tag="Id", _G.string.format(".%i.%s", gensymidx, arg or "")}
end
--------------------------------------------------------------------------------
-- Converts an identifier into a string. Hopefully one day it'll handle
-- splices gracefully, but that proves quite tricky.
--------------------------------------------------------------------------------
function id2string (id)
--print("id2string:", disp.ast(id))
if id.tag == "Id" then id.tag = "String"; return id
elseif id.tag == "Splice" then
assert (in_a_quote, "can't do id2string on an outermost splice")
error ("id2string on splice not implemented")
-- Evaluating id[1] will produce `Id{ xxx },
-- and we want it to produce `String{ xxx }
-- Morally, this is what I want:
-- return `String{ `Index{ `Splice{ id[1] }, `Number 1 } }
-- That is, without sugar:
return {tag="String", {tag="Index", {tag="Splice", id[1] },
{tag="Number", 1 } } }
else error ("Identifier expected: ".._G.table.tostring(id, 'nohash')) end
end
--------------------------------------------------------------------------------
-- Read a string, possibly spliced, or return an error if it can't
--------------------------------------------------------------------------------
function string (lx)
local a = lx:peek()
if lx:is_keyword (a, "-{") then
local v = gg.sequence{ "-{", splice_content, "}" } (lx) [1]
if v.tag ~= "" and v.tag ~= "Splice" then
gg.parse_error(lx,"Bad string splice")
end
return v
elseif a.tag == "String" then return lx:next()
else error "String expected" end
end
--------------------------------------------------------------------------------
-- Try to read a string, or return false if it can't. No splice allowed.
--------------------------------------------------------------------------------
function opt_string (lx)
return lx:peek().tag == "String" and lx:next()
end
--------------------------------------------------------------------------------
-- Chunk reader: block + Eof
--------------------------------------------------------------------------------
function skip_initial_sharp_comment (lx)
-- Dirty hack: I'm happily fondling lexer's private parts
-- FIXME: redundant with lexer:newstream()
lx :sync()
local i = lx.src:match ("^#.-\n()", lx.i)
if i then lx.i, lx.column_offset, lx.line = i, i, lx.line+1 end
end
local function _chunk (lx)
if lx:peek().tag == 'Eof' then return { } -- handle empty files
else
skip_initial_sharp_comment (lx)
local chunk = block (lx)
if lx:peek().tag ~= "Eof" then error "End-of-file expected" end
return chunk
end
end
-- chunk is wrapped in a sequence so that it has a "transformer" field.
chunk = gg.sequence { _chunk, builder = unpack }

View File

@@ -0,0 +1,226 @@
----------------------------------------------------------------------
-- Metalua: $Id: mlp_stat.lua,v 1.7 2006/11/15 09:07:50 fab13n Exp $
--
-- Summary: metalua parser, statement/block parser. This is part of
-- the definition of module [mlp].
--
----------------------------------------------------------------------
--
-- Copyright (c) 2006, Fabien Fleutot <metalua@gmail.com>.
--
-- This software is released under the MIT Licence, see licence.txt
-- for details.
--
----------------------------------------------------------------------
--
----------------------------------------------------------------------
--------------------------------------------------------------------------------
--
-- Exports API:
-- * [mlp.stat()]
-- * [mlp.block()]
-- * [mlp.for_header()]
--
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- eta-expansions to break circular dependency
--------------------------------------------------------------------------------
local expr = function (lx) return mlp.expr (lx) end
local func_val = function (lx) return mlp.func_val (lx) end
local expr_list = function (lx) return mlp.expr_list(lx) end
module ("mlp", package.seeall)
--------------------------------------------------------------------------------
-- List of all keywords that indicate the end of a statement block. Users are
-- likely to extend this list when designing extensions.
--------------------------------------------------------------------------------
local block_terminators = { "else", "elseif", "end", "until", ")", "}", "]" }
-- FIXME: this must be handled from within GG!!!
function block_terminators:add(x)
if type (x) == "table" then for _, y in ipairs(x) do self:add (y) end
else _G.table.insert (self, x) end
end
--------------------------------------------------------------------------------
-- list of statements, possibly followed by semicolons
--------------------------------------------------------------------------------
block = gg.list {
name = "statements block",
terminators = block_terminators,
primary = function (lx)
-- FIXME use gg.optkeyword()
local x = stat (lx)
if lx:is_keyword (lx:peek(), ";") then lx:next() end
return x
end }
--------------------------------------------------------------------------------
-- Helper function for "return <expr_list>" parsing.
-- Called when parsing return statements.
-- The specific test for initial ";" is because it's not a block terminator,
-- so without itgg.list would choke on "return ;" statements.
-- We don't make a modified copy of block_terminators because this list
-- is sometimes modified at runtime, and the return parser would get out of
-- sync if it was relying on a copy.
--------------------------------------------------------------------------------
local return_expr_list_parser = gg.multisequence{
{ ";" , builder = function() return { } end },
default = gg.list {
expr, separators = ",", terminators = block_terminators } }
--------------------------------------------------------------------------------
-- for header, between [for] and [do] (exclusive).
-- Return the `Forxxx{...} AST, without the body element (the last one).
--------------------------------------------------------------------------------
function for_header (lx)
local var = mlp.id (lx)
if lx:is_keyword (lx:peek(), "=") then
-- Fornum: only 1 variable
lx:next() -- skip "="
local e = expr_list (lx)
assert (2 <= #e and #e <= 3, "2 or 3 values in a fornum")
return { tag="Fornum", var, unpack (e) }
else
-- Forin: there might be several vars
local a = lx:is_keyword (lx:next(), ",", "in")
if a=="in" then var_list = { var, lineinfo = var.lineinfo } else
-- several vars; first "," skipped, read other vars
var_list = gg.list{
primary = id, separators = ",", terminators = "in" } (lx)
_G.table.insert (var_list, 1, var) -- put back the first variable
lx:next() -- skip "in"
end
local e = expr_list (lx)
return { tag="Forin", var_list, e }
end
end
--------------------------------------------------------------------------------
-- Function def parser helper: id ( . id ) *
--------------------------------------------------------------------------------
local function fn_builder (list)
local r = list[1]
for i = 2, #list do r = { tag="Index", r, id2string(list[i]) } end
return r
end
local func_name = gg.list{ id, separators = ".", builder = fn_builder }
--------------------------------------------------------------------------------
-- Function def parser helper: ( : id )?
--------------------------------------------------------------------------------
local method_name = gg.onkeyword{ name = "method invocation", ":", id,
transformers = { function(x) return x and id2string(x) end } }
--------------------------------------------------------------------------------
-- Function def builder
--------------------------------------------------------------------------------
local function funcdef_builder(x)
local name, method, func = x[1], x[2], x[3]
if method then
name = { tag="Index", name, method, lineinfo = {
first = name.lineinfo.first,
last = method.lineinfo.last } }
_G.table.insert (func[1], 1, {tag="Id", "self"})
end
local r = { tag="Set", {name}, {func} }
r[1].lineinfo = name.lineinfo
r[2].lineinfo = func.lineinfo
return r
end
--------------------------------------------------------------------------------
-- if statement builder
--------------------------------------------------------------------------------
local function if_builder (x)
local cb_pairs, else_block, r = x[1], x[2], {tag="If"}
for i=1,#cb_pairs do r[2*i-1]=cb_pairs[i][1]; r[2*i]=cb_pairs[i][2] end
if else_block then r[#r+1] = else_block end
return r
end
--------------------------------------------------------------------------------
-- produce a list of (expr,block) pairs
--------------------------------------------------------------------------------
local elseifs_parser = gg.list {
gg.sequence { expr, "then", block },
separators = "elseif",
terminators = { "else", "end" } }
--------------------------------------------------------------------------------
-- assignments and calls: statements that don't start with a keyword
--------------------------------------------------------------------------------
local function assign_or_call_stat_parser (lx)
local e = expr_list (lx)
local a = lx:is_keyword(lx:peek())
local op = a and stat.assignments[a]
if op then
--FIXME: check that [e] is a LHS
lx:next()
local v = expr_list (lx)
if type(op)=="string" then return { tag=op, e, v }
else return op (e, v) end
else
assert (#e > 0)
if #e > 1 then
gg.parse_error (lx,
"comma is not a valid statement separator; statement can be "..
"separated by semicolons, or not separated at all") end
if e[1].tag ~= "Call" and e[1].tag ~= "Invoke" then
local typename
if e[1].tag == 'Id' then
typename = '("'..e[1][1]..'") is an identifier'
elseif e[1].tag == 'Op' then
typename = "is an arithmetic operation"
else typename = "is of type '"..(e[1].tag or "<list>").."'" end
gg.parse_error (lx, "This expression " .. typename ..
"; a statement was expected, and only function and method call "..
"expressions can be used as statements");
end
return e[1]
end
end
local_stat_parser = gg.multisequence{
-- local function <name> <func_val>
{ "function", id, func_val, builder =
function(x)
local vars = { x[1], lineinfo = x[1].lineinfo }
local vals = { x[2], lineinfo = x[2].lineinfo }
return { tag="Localrec", vars, vals }
end },
-- local <id_list> ( = <expr_list> )?
default = gg.sequence{ id_list, gg.onkeyword{ "=", expr_list },
builder = function(x) return {tag="Local", x[1], x[2] or { } } end } }
--------------------------------------------------------------------------------
-- statement
--------------------------------------------------------------------------------
stat = gg.multisequence {
name="statement",
{ "do", block, "end", builder =
function (x) return { tag="Do", unpack (x[1]) } end },
{ "for", for_header, "do", block, "end", builder =
function (x) x[1][#x[1]+1] = x[2]; return x[1] end },
{ "function", func_name, method_name, func_val, builder=funcdef_builder },
{ "while", expr, "do", block, "end", builder = "While" },
{ "repeat", block, "until", expr, builder = "Repeat" },
{ "local", local_stat_parser, builder = fget (1) },
{ "return", return_expr_list_parser, builder = fget (1, "Return") },
{ "break", builder = function() return { tag="Break" } end },
{ "-{", splice_content, "}", builder = fget(1) },
{ "if", elseifs_parser, gg.onkeyword{ "else", block }, "end",
builder = if_builder },
default = assign_or_call_stat_parser }
stat.assignments = {
["="] = "Set" }
function stat.assignments:add(k, v) self[k] = v end

View File

@@ -0,0 +1,92 @@
----------------------------------------------------------------------
-- Metalua: $Id: mlp_table.lua,v 1.5 2006/11/10 02:11:17 fab13n Exp $
--
-- Summary: metalua parser, table constructor parser. This is part
-- of thedefinition of module [mlp].
--
----------------------------------------------------------------------
--
-- Copyright (c) 2006, Fabien Fleutot <metalua@gmail.com>.
--
-- This software is released under the MIT Licence, see licence.txt
-- for details.
--
----------------------------------------------------------------------
-- History:
-- $Log: mlp_table.lua,v $
-- Revision 1.5 2006/11/10 02:11:17 fab13n
-- compiler faithfulness to 5.1 improved
-- gg.expr extended
-- mlp.expr refactored
--
-- Revision 1.4 2006/11/09 09:39:57 fab13n
-- some cleanup
--
-- Revision 1.3 2006/11/07 04:38:00 fab13n
-- first bootstrapping version.
--
-- Revision 1.2 2006/11/05 15:08:34 fab13n
-- updated code generation, to be compliant with 5.1
--
----------------------------------------------------------------------
--------------------------------------------------------------------------------
--
-- Exported API:
-- * [mlp.table_field()]
-- * [mlp.table_content()]
-- * [mlp.table()]
--
-- KNOWN BUG: doesn't handle final ";" or "," before final "}"
--
--------------------------------------------------------------------------------
--require "gg"
--require "mll"
--require "mlp_misc"
module ("mlp", package.seeall)
--------------------------------------------------------------------------------
-- eta expansion to break circular dependencies:
--------------------------------------------------------------------------------
local function _expr (lx) return expr(lx) end
--------------------------------------------------------------------------------
-- [[key] = value] table field definition
--------------------------------------------------------------------------------
local bracket_field = gg.sequence{ "[", _expr, "]", "=", _expr, builder = "Pair" }
--------------------------------------------------------------------------------
-- [id = value] or [value] table field definition;
-- [[key]=val] are delegated to [bracket_field()]
--------------------------------------------------------------------------------
function table_field (lx)
if lx:is_keyword (lx:peek(), "[") then return bracket_field (lx) end
local e = _expr (lx)
if lx:is_keyword (lx:peek(), "=") then
lx:next(); -- skip the "="
local key = id2string(e)
local val = _expr(lx)
local r = { tag="Pair", key, val }
r.lineinfo = { first = key.lineinfo.first, last = val.lineinfo.last }
return r
else return e end
end
local function _table_field(lx) return table_field(lx) end
--------------------------------------------------------------------------------
-- table constructor, without enclosing braces; returns a full table object
--------------------------------------------------------------------------------
table_content = gg.list { _table_field,
separators = { ",", ";" }, terminators = "}", builder = "Table" }
local function _table_content(lx) return table_content(lx) end
--------------------------------------------------------------------------------
-- complete table constructor including [{...}]
--------------------------------------------------------------------------------
table = gg.sequence{ "{", _table_content, "}", builder = fget(1) }

File diff suppressed because it is too large Load Diff

93
lualibs/ssl.lua Normal file
View File

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

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

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

315
lualibs/testwell.lua Normal file
View File

@@ -0,0 +1,315 @@
--
-- Copyright (C) 2012 Paul Kulchenko
-- A simple testing library
-- Based on lua-TestMore : <http://fperrad.github.com/lua-TestMore/>
-- Copyright (c) 2009-2011 Francois Perrad
-- This library is licensed under the terms of the MIT/X11 license,
-- like Lua itself.
--
local pairs = pairs
local tostring = tostring
local type = type
local _G = _G or _ENV
-----------------------------------------------------------
local tb = {
curr_test = 0,
good_test = 0,
skip_test = 0,
}
function tb:print(...)
print(...)
end
function tb:note(...)
self:print(...)
end
function tb:diag(...)
local arg = {...}
for k, v in pairs(arg) do
arg[k] = tostring(v)
end
local msg = table.concat(arg)
msg = msg:gsub("\n", "\n# ")
msg = msg:gsub("\n# \n", "\n#\n")
msg = msg:gsub("\n# $", '')
self:print("# " .. msg)
end
function tb:ok(test, name, more)
self.curr_test = self.curr_test + 1
self.good_test = self.good_test + (test and 1 or 0)
self.skip_test = self.skip_test + (test == nil and 1 or 0)
name = tostring(name or '')
local out = ''
if not test then
out = "not "
end
out = out .. "ok " .. self.curr_test
if name ~= '' then
out = out .. " - " .. name
end
self:print(out)
if test == false then
self:diag(" Failed test " .. (name and ("'" .. name .. "'") or ''))
if debug then
local info = debug.getinfo(3)
local file = info.short_src
local line = info.currentline
self:diag(" in " .. file .. " at line " .. line .. ".")
end
self:diag(more)
end
end
function tb:done_testing(reset)
local c, g, s = self.curr_test, self.good_test, self.skip_test
if reset then
self.curr_test = 0
self.good_test = 0
self.skip_test = 0
end
return c, g, s
end
-----------------------------------------------------------
local serpent = (function() ---- include Serpent module for serialization
local n, v = "serpent", 0.15 -- (C) 2012 Paul Kulchenko; MIT License
local c, d = "Paul Kulchenko", "Serializer and pretty printer of Lua data types"
local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'}
local badtype = {thread = true, userdata = true}
local keyword, globals, G = {}, {}, (_G or _ENV)
for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false',
'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end
for k,v in pairs(G) do globals[v] = k end -- build func to name mapping
for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do
for k,v in pairs(G[g]) do globals[v] = g..'.'..k end end
local function s(t, opts)
local name, indent, fatal = opts.name, opts.indent, opts.fatal
local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge
local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge)
local comm = opts.comment and (tonumber(opts.comment) or math.huge)
local seen, sref, syms, symn = {}, {}, {}, 0
local function gensym(val) return tostring(val):gsub("[^%w]",""):gsub("(%d%w+)",
function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return syms[s] end) end
local function safestr(s) return type(s) == "number" and (huge and snum[tostring(s)] or s)
or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026
or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end
local function comment(s,l) return comm and (l or 0) < comm and ' --[['..tostring(s)..']]' or '' end
local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal
and safestr(tostring(s))..comment('err',l) or error("Can't serialize "..tostring(s)) end
local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r']
local n = name == nil and '' or name
local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n]
local safe = plain and n or '['..safestr(n)..']'
return (path or '')..(plain and path and '.' or '')..safe, safe end
local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(o, n)
local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'}
local function padnum(d) return ("%0"..maxn.."d"):format(d) end
table.sort(o, function(a,b)
return (o[a] and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum))
< (o[b] and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end
local function val2str(t, name, indent, path, plainindex, level)
local ttype, level = type(t), (level or 0)
local spath, sname = safename(path, name)
local tag = plainindex and
((type(name) == "number") and '' or name..space..'='..space) or
(name ~= nil and sname..space..'='..space or '')
if seen[t] then
table.insert(sref, spath..space..'='..space..seen[t])
return tag..'nil'..comment('ref', level)
elseif badtype[ttype] then return tag..globerr(t, level)
elseif ttype == 'function' then
seen[t] = spath
local ok, res = pcall(string.dump, t)
local func = ok and ((opts.nocode and "function() end" or
"loadstring("..safestr(res)..",'@serialized')")..comment(t, level))
return tag..(func or globerr(t, level))
elseif ttype == "table" then
if level >= maxl then return tag..'{}'..comment('max', level) end
seen[t] = spath
if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty
local maxn, o, out = #t, {}, {}
for key = 1, maxn do table.insert(o, key) end
for key in pairs(t) do if not o[key] then table.insert(o, key) end end
if opts.sortkeys then alphanumsort(o, opts.sortkeys) end
for n, key in ipairs(o) do
local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse
if opts.ignore and opts.ignore[value] -- skip ignored values; do nothing
or sparse and value == nil then -- skipping nils; do nothing
elseif ktype == 'table' or ktype == 'function' then
if not seen[key] and not globals[key] then
table.insert(sref, 'local '..val2str(key,gensym(key),indent)) end
table.insert(sref, seen[t]..'['..(seen[key] or globals[key] or gensym(key))
..']'..space..'='..space..(seen[value] or val2str(value,nil,indent)))
else
if badtype[ktype] then plainindex, key = true, '['..globerr(key, level+1)..']' end
table.insert(out,val2str(value,key,indent,spath,plainindex,level+1))
end
end
local prefix = string.rep(indent or '', level)
local head = indent and '{\n'..prefix..indent or '{'
local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space))
local tail = indent and "\n"..prefix..'}' or '}'
return (custom and custom(tag,head,body,tail) or tag..head..body..tail)..comment(t, level)
else return tag..safestr(t) end -- handle all other types
end
local sepr = indent and "\n" or ";"..space
local body = val2str(t, name, indent) -- this call also populates sref
local tail = #sref>0 and table.concat(sref, sepr)..sepr or ''
return not name and body or "do local "..body..sepr..tail.."return "..name..sepr.."end"
end
local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end
return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s,
dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end,
line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end,
block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end }
end)() ---- end of Serpent module
-----------------------------------------------------------
local m = {}
function m.ok(test, name)
tb:ok(test, name)
end
local parms = {comment = false}
function m.is(got, expected, name)
if type(got) == 'table' then got = serpent.line(got, parms) end
if type(expected) == 'table' then expected = serpent.line(expected, parms) end
local pass = got == expected
if got == nil then pass = nil end
tb:ok(pass, name, not pass and
" got: " .. tostring(got) ..
"\n expected: " .. tostring(expected))
end
function m.isnt(got, expected, name)
if type(got) == 'table' then got = serpent.line(got, parms) end
if type(expected) == 'table' then expected = serpent.line(expected, parms) end
local pass = got ~= expected
if got == nil then pass = nil end
tb:ok(pass, name, not pass and
" got: " .. tostring(got) ..
"\n expected: anything else")
end
function m.like(got, pattern, name)
if type(pattern) ~= 'string' then
return tb:ok(false, name, "pattern isn't a string : " .. tostring(pattern))
end
local pass = tostring(got):match(pattern)
if got == nil then pass = nil end
tb:ok(pass, name, not pass and
" '" .. tostring(got) .. "'" ..
"\n doesn't match '" .. pattern .. "'")
end
function m.unlike(got, pattern, name)
if type(pattern) ~= 'string' then
return tb:ok(false, name, "pattern isn't a string : " .. tostring(pattern))
end
local pass = not tostring(got):match(pattern)
if got == nil then pass = nil end
tb:ok(pass, name, not pass and
" '" .. tostring(got) .. "'" ..
"\n matches '" .. pattern .. "'")
end
local cmp = {
['<'] = function (a, b) return a < b end,
['<='] = function (a, b) return a <= b end,
['>'] = function (a, b) return a > b end,
['>='] = function (a, b) return a >= b end,
['=='] = function (a, b) return a == b end,
['~='] = function (a, b) return a ~= b end,
}
function m.cmp_ok(this, op, that, name)
local f = cmp[op]
if not f then
return tb:ok(false, name, "unknown operator : " .. tostring(op))
end
local pass = f(this, that)
if this == nil then pass = nil end
tb:ok(pass, name, not pass and
" " .. tostring(this) ..
"\n " .. op ..
"\n " .. tostring(that))
end
function m.type_ok(val, t, name)
if type(t) ~= 'string' then
return tb:ok(false, name, "type isn't a string : " .. tostring(t))
end
if type(val) == t then
tb:ok(true, name)
else
tb:ok(false, name,
" " .. tostring(val) .. " isn't a '" .. t .."', it's a '" .. type(val) .. "'")
end
end
function m.diag(...)
tb:diag(...)
end
function m.report()
local total, good, skipped = tb:done_testing(true)
if total == 0 then return end
local failed = total - good - skipped
local sum = ("(%d/%d/%d)."):format(good, skipped, total)
local num, msg = 0, ""
if good > 0 then
num, msg = good, msg .. "passed " .. good
end
if failed > 0 then
num, msg = failed, msg .. (#msg > 0 and (skipped > 0 and ", " or " and ") or "")
.. "failed " .. failed
end
if skipped > 0 then
num, msg = skipped, msg .. (#msg > 0 and ((good > 0 and failed > 0 and ',' or '') .." and ") or "")
.. "skipped " .. skipped
end
msg = ("Looks like you %s test%s of %d %s"):format(msg, (num > 1 and 's' or ''), total, sum)
if skipped == total then msg = "Looks like you skipped all tests " .. sum end
if good == total then msg = "All tests passed " .. sum end
tb:note(("1..%d # %s"):format(total, msg))
end
function m.ismain()
for l = 3, 64 do -- only check up to 64 level; no more needed
local info = debug.getinfo(l)
if not info then return true end
if info.func == require then return false end
end
return true
end
-- this is needed to call report() when the test object is destroyed
if _VERSION >= "Lua 5.2" then
setmetatable(m, {__gc = m.report})
else
-- keep sentinel alive until 'm' is garbage collected
m.sentinel = newproxy(true)
getmetatable(m.sentinel).__gc = m.report
end
for k, v in pairs(m) do -- injection
_G[k] = v
end
return m

View File

@@ -1,6 +1,8 @@
-- authors: Luxinia Dev (Eike Decker & Christoph Kubisch)
---------------------------------------------------------
local funcdef = "([A-Za-z_][A-Za-z0-9_%.%:]*)%s*"
local funccall = "([A-Za-z_][A-Za-z0-9_]*)%s*"
return {
exts = {"lua"},
lexer = wxstc.wxSTC_LEX_LUA,
@@ -8,23 +10,18 @@ return {
linecomment = "--",
sep = "%.:",
isfncall = function(str)
return string.find(str,"([A-Za-z0-9_]+)%s*%(")
return string.find(str, funccall .. "%(")
end,
isfndef = function(str)
local l
local s,e,cap,par = string.find(str,"function%s+([A-Za-z0-9_]+%s-[%.%:]?%s-[A-Za-z0-9_]*)%s*(%(.-%))")
local s,e,cap,par = string.find(str, "function%s+" .. funcdef .. "(%(.-%))")
-- try to match without brackets now, but only at the beginning of the line
if (not s) then
s,e,cap = string.find(str,"^%s*function%s+([A-Za-z0-9_]+%s-[%.%:]?%s-[A-Za-z0-9_]*)%s*")
s,e,cap = string.find(str, "^%s*function%s+" .. funcdef)
end
-- try to match "foo = function()"
if (not s) then
s,e,cap,cap1,cap2,par = string.find(str,"(([A-Za-z0-9_]+%s-[%.%:]?%s-)([A-Za-z0-9_]*))%s*=%s*function%s*(%(.-%))")
-- check if we captured 'local foo =' instead of 'foo.bar ='
if cap1 and cap2 and string.len(cap2) > 0 and not string.find(cap1,'[%.%:]') then
cap = cap2
s = s + string.len(cap1)
end
s,e,cap,par = string.find(str, funcdef .. "=%s*function%s*(%(.-%))")
end
if (s) then
l = string.find(string.sub(str,1,s-1),"local%s+$")
@@ -112,13 +109,6 @@ return {
line = line+1
end
if (added) then
DisplayOutput("\nTYPES\n")
for i,v in pairs(assigns) do
DisplayOutput(i,v,"\n")
end
end
return assigns
end,

437
spec/ptx.lua Normal file
View File

@@ -0,0 +1,437 @@
-- author: Christoph Kubisch
---------------------------------------------------------
return {
exts = {"ptx",},
lexer = wxstc.wxSTC_LEX_CPP,
apitype = "ptx",
sep = "%.",
linecomment = "//",
isfndef = function(str)
local l
local s,e,cap = string.find(str,"^%s*([A-Za-z0-9_]+%s+[A-Za-z0-9_]+%s*%(.+%))")
if (not s) then
s,e,cap = string.find(str,"^%s*([A-Za-z0-9_]+%s+[A-Za-z0-9_]+)%s*%(")
end
if (cap and (string.find(cap,"^return") or string.find(cap,"else"))) then return end
return s,e,cap,l
end,
lexerstyleconvert = {
text = {wxstc.wxSTC_C_IDENTIFIER,
wxstc.wxSTC_C_VERBATIM,
wxstc.wxSTC_C_REGEX,
wxstc.wxSTC_C_REGEX,
wxstc.wxSTC_C_GLOBALCLASS,},
lexerdef = {wxstc.wxSTC_C_DEFAULT,},
comment = {wxstc.wxSTC_C_COMMENT,
wxstc.wxSTC_C_COMMENTLINE,
wxstc.wxSTC_C_COMMENTDOC,
wxstc.wxSTC_C_COMMENTLINEDOC,
wxstc.wxSTC_C_COMMENTDOCKEYWORD,
wxstc.wxSTC_C_COMMENTDOCKEYWORDERROR,},
stringtxt = {wxstc.wxSTC_C_STRING,
wxstc.wxSTC_C_CHARACTER,
wxstc.wxSTC_C_UUID,},
stringeol = {wxstc.wxSTC_C_STRINGEOL,},
preprocessor= {wxstc.wxSTC_C_PREPROCESSOR,},
operator = {wxstc.wxSTC_C_OPERATOR,},
number = {wxstc.wxSTC_C_NUMBER,
wxstc.wxSTC_C_WORD},
keywords0 = {wxstc.wxSTC_C_WORD,},
keywords1 = {wxstc.wxSTC_C_WORD2,},
},
keywords = {
[[
version
target
address_size
entry
func
branchtargets
calltargets
callprototype
maxnreg
maxntid
reqntid
minnctapersm
maxnctapersm
pragma
section
file
loc
extern
visible
pragma
align
file
maxntid
shared
branchtargets
func
minnctapersm
sreg
callprototype
global
param
target
calltargets
local
pragma
tex
const
loc
reg
version
entry
maxnctapersm
reqntid
visible
extern
maxnreg
section
s8
s16
s32
s64
u8
u16
u32
u64
f16
f32
f64
b8
b16
b32
b64
pred
rn
rz
rm
rp
rni
rzi
rmi
rpi
ca
cg
cs
lu
cv
wb
cg
cs
wt
texref
samplerref
surfref
sat
ftz
cc
hi
lo
wide
f4e
b4e
rc8
ecl
ecr
rc16
finite
infinite
number
notanumber
normal
subnormal
approx
full
eq
ne
lt
le
gt
ge
equ
neu
ltu
leu
gtu
geu
num
nan
ls
hs
volatile
v2
v4
L1
L2
1d
2d
3d
a1d
a2d
width
height
depth
channel_data_type
channel_order
normalized_coords
force_unnormalized_coords
filter_mode
addr_mode_0
addr_mode_1
addr_mode_2
trap
clamp
zero
all
any
uni
ballot
sync
arrive
red
cta
gl
sys
and
or
xor
cas
exch
add
inc
dec
min
max
b0
b1
b2
b3
h0
h1
wrap
shr7
shr15
byte
4byte
quad
4byte
quad
b8
b32
b64
b32
b64
]],
-- functions
[[
add
sub
add.cc
addc
sub.cc
subc
mul
mad
mul24
mad24
sad
div
rem
abs
neg
min
max
popc
clz
bfind
brev
bfe
bfi
prmt
rcp
sqrt
rsqrt
sin
cos
lg2
ex2
fma
set
setp
selp
slct
and
or
xor
not
cnot
shl
shr
mov
ld
ldu
st
prefetch
prefetchu
isspacep
cvta
cvt
tex
tld4
txq
suld
sust
sured
suq
bra
call
ret
exit
bar
membar
atom
red
vote
vadd
vsub
vabsdiff
vmin
vmax
vshl
vshr
vmad
vset
trap
brkpt
pmevent
%clock
%laneid
%lanemask_gt
%pm0
%pm1
%pm2
%pm3
%clock64
%lanemask_eq
%nctaid
%smid
%ctaid
%lanemask_le
%ntid
%tid
%envreg0
%envreg1
%envreg2
%envreg3
%envreg4
%envreg5
%envreg6
%envreg7
%envreg8
%envreg9
%envreg10
%envreg11
%envreg12
%envreg13
%envreg14
%envreg15
%envreg16
%envreg17
%envreg18
%envreg19
%envreg20
%envreg21
%envreg22
%envreg23
%envreg24
%envreg25
%envreg26
%envreg27
%envreg28
%envreg29
%envreg30
%envreg31
%lanemask_lt
%nsmid
%warpid
%gridid
%lanemask_ge
%nwarpid
WARP_SZ
nearest
linear
wrap
mirror
clamp_ogl
clamp_to_edge
clamp_to_border
sm_20
sm_10
sm_11
sm_12
sm_13
texmode_unified
texmode_independent
map_f64_to_f32
]],
},
}

View File

@@ -13,7 +13,7 @@
-- style definition
-- ----------------------------------------------------
-- all entries are optiona
-- all entries are optional
stattr = {
fg = {r,g,b}, -- foreground color 0-255
bg = {r,g,b}, -- background color
@@ -21,6 +21,13 @@ stattr = {
b = false, -- bold
u = false, -- underline
fill = true, -- fill to lineend
-- fn = "Lucida Console", -- font Face Name
-- fx = 11, -- font size
-- hs = true or {r,g,b}, -- turn hotspot on
-- use the specified color as activeForeground
-- use "hs = true", to turn it on without changing the color
-- HotspotActiveUnderline and HotspotSingleLine are on automatically
-- v = true, -- visibility for symbols of the current style
}
style = {
@@ -159,6 +166,10 @@ config = {
singleinstanceport = 0xe493,
-- UDP port for single instance communication
activateoutput = false, -- activate output/console on Run/Debug/Compile
unhidewxwindow = false, -- try to unhide a wx window
allowinteractivescript = false, -- allow interaction in the output window
}
-- application engine
@@ -299,12 +310,10 @@ debuginterface = {
interpreter = {
name = "",
description = "",
api = {"apifile_without_extension"} -- optional to limit loaded lua apis
frun = function(self,wfilename,withdebugger)
end,
fprojdir = function(self,wfilename)
return "projpath_from_filename" -- optional
end,
fattachdebug = function(self) end, -- optional
api = {"apifile_without_extension"} -- (opt) to limit loaded lua apis
frun = function(self,wfilename,withdebugger) end,
fprojdir = function(self,wfilename) return "projpath_from_filename" end, -- (opt)
fattachdebug = function(self) end, -- (opt)
hasdebugger = false, -- if debugging is available
scratchextloop = false, -- (opt) if scratchpad requires handling for external loop
}

View File

@@ -89,8 +89,8 @@ local function addAPI(apifile,only,subapis,known) -- relative to API directory
end
local function loadallAPIs (only,subapis,known)
for i,dir in ipairs(FileSysGet(".\\api\\*.*",wx.wxDIR)) do
local files = FileSysGet(dir.."\\*.*",wx.wxFILE)
for i,dir in ipairs(FileSysGet("./api/*",wx.wxDIR)) do
local files = FileSysGet(dir.."/*.*",wx.wxFILE)
for i,file in ipairs(files) do
if file:match "%.lua$" then
addAPI(file,only,subapis,known)
@@ -239,7 +239,7 @@ end
function GetTipInfo(editor, content, short)
local caller = content:match("([%w_]+)%(%s*$")
local class = caller and content:match("([%w_%.]+)[%.:]"..caller.."%(%s*$")
local class = caller and content:match("([%w_]+)[%.:]"..caller.."%(%s*$")
local tip = editor.api.tip
local classtab = short and tip.shortfinfoclass or tip.finfoclass

View File

@@ -5,6 +5,7 @@ local ide = ide
local frame = ide.frame
local notebook = frame.notebook
local openDocuments = ide.openDocuments
local uimgr = frame.uimgr
function NewFile(event)
local editor = CreateEditor("untitled.lua")
@@ -28,7 +29,7 @@ end
function LoadFile(filePath, editor, file_must_exist)
filePath = wx.wxFileName(filePath):GetFullPath()
local cmpName = string.lower(string.gsub(filePath, "\\", "/"))
-- prevent files from being reopened again
if (not editor) then
for id, doc in pairs(openDocuments) do
@@ -53,12 +54,10 @@ 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
local current = editor and editor:GetCurrentPos()
editor = editor
or findDocumentToReuse()
or CreateEditor(wx.wxFileName(filePath):GetFullName() or "untitled.lua")
editor:Clear()
editor:ClearAll()
@@ -66,6 +65,7 @@ function LoadFile(filePath, editor, file_must_exist)
editor:MarkerDeleteAll(BREAKPOINT_MARKER)
editor:MarkerDeleteAll(CURRENT_LINE_MARKER)
editor:AppendText(file_text)
if current then editor:GotoPos(current) end
if (ide.config.editor.autotabs) then
local found = string.find(file_text,"\t") ~= nil
editor:SetUseTabs(found)
@@ -82,8 +82,10 @@ function LoadFile(filePath, editor, file_must_exist)
IndicateFunctions(editor)
SettingsAppendFileToHistory(filePath)
SetEditorSelection(nil)
-- activate the editor; this is needed for those cases when the editor is
-- created from some other element, for example, from a project tree.
SetEditorSelection()
return editor
end
@@ -113,11 +115,11 @@ function OpenFile(event)
"",
"",
exts,
wx.wxOPEN + wx.wxFILE_MUST_EXIST)
wx.wxFD_OPEN + wx.wxFD_FILE_MUST_EXIST)
if fileDialog:ShowModal() == wx.wxID_OK then
if not LoadFile(fileDialog:GetPath(), nil, true) then
wx.wxMessageBox("Unable to load file '"..fileDialog:GetPath().."'.",
"wxLua Error",
"Error",
wx.wxOK + wx.wxCENTRE, ide.frame)
end
end
@@ -129,8 +131,6 @@ function SaveFile(editor, filePath)
if not filePath then
return SaveFileAs(editor)
else
filePath = filePath:gsub("\\","/")
if (ide.config.savebak) then
local backPath = filePath..".bak"
os.remove(backPath)
@@ -156,7 +156,7 @@ function SaveFile(editor, filePath)
return true
else
wx.wxMessageBox("Unable to save file '"..filePath.."'.",
"wxLua Error Saving",
"Error",
wx.wxOK + wx.wxCENTRE, ide.frame)
end
end
@@ -182,14 +182,18 @@ function SaveFileAs(editor)
fn:GetPath(wx.wxPATH_GET_VOLUME),
fn:GetFullName(),
exts,
wx.wxSAVE)
wx.wxFD_SAVE)
if fileDialog:ShowModal() == wx.wxID_OK then
local filePath = fileDialog:GetPath()
if SaveFile(editor, filePath) then
SetEditorSelection() -- update title of the editor
FileTreeRefresh() -- refresh the tree to reflect the new file
FileTreeMarkSelected(filePath)
SetupKeywords(editor, GetFileExt(filePath))
IndicateFunctions(editor)
if MarkupStyle then MarkupStyle(editor) end
saved = true
end
end
@@ -218,7 +222,10 @@ local function removePage(index)
selectIndex = selectIndex ~= index and selectIndex
local delid = nil
for id, document in pairs(openDocuments) do
for id, document in pairsSorted(openDocuments,
function(a, b) -- sort by document index
return openDocuments[a].index < openDocuments[b].index
end) do
local wasselected = document.index == selectIndex
if document.index < index then
prevIndex = document.index
@@ -250,7 +257,7 @@ local function removePage(index)
notebook:SetSelection(prevIndex)
end
SetEditorSelection(nil) -- will use notebook GetSelection to update
SetEditorSelection() -- will use notebook GetSelection to update
end
function ClosePage(selection)
@@ -258,6 +265,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
@@ -271,12 +291,7 @@ function SaveModifiedDialog(editor, allow_cancel)
local filePath = document.filePath
local fileName = document.fileName
if document.isModified then
local message
if fileName then
message = "Save changes to '"..fileName.."' before exiting?"
else
message = "Save changes to 'untitled' before exiting?"
end
local message = "Do you want to save the changes to '"..(fileName or 'untitled').."'?"
local dlg_styles = wx.wxYES_NO + wx.wxCENTRE + wx.wxICON_QUESTION
if allow_cancel then dlg_styles = dlg_styles + wx.wxCANCEL end
local dialog = wx.wxMessageDialog(ide.frame, message,
@@ -299,8 +314,13 @@ function SaveOnExit(allow_cancel)
if (SaveModifiedDialog(document.editor, allow_cancel) == wx.wxID_CANCEL) then
return false
end
end
document.isModified = false
-- if all documents have been saved or refused to save, then mark those that
-- are still modified as not modified (they don't need to be saved)
-- to keep their tab names correct
for id, document in pairs(openDocuments) do
if document.isModified then SetDocumentModified(id, false) end
end
return true
@@ -385,20 +405,23 @@ function ClearAllCurrentLineMarkers()
end
end
function CompileProgram(editor)
local editorText = editor:GetText()
function CompileProgram(editor, quiet)
-- remove shebang line (#!) as it throws a compilation error as
-- loadstring() doesn't allow it even though lua/loadfile accepts it.
-- replace with a new line to keep the number of lines the same.
local editorText = editor:GetText():gsub("^#!.-\n", "\n")
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
@@ -470,23 +493,47 @@ function SetOpenFiles(nametab,index)
notebook:SetSelection(index or 0)
end
local beforeFullScreenPerspective
function ShowFullScreen(setFullScreen)
if setFullScreen then
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
end
uimgr:GetPane("toolBar"):Show(not setFullScreen)
uimgr:Update()
-- protect from systems that don't have ShowFullScreen (GTK on linux?)
pcall(function() frame:ShowFullScreen(setFullScreen) end)
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
ShowFullScreen(false)
SettingsSaveProjectSession(FileTreeGetProjects())
SettingsSaveFileSession(GetOpenFiles())
SettingsSaveView()
SettingsSaveFramePosition(ide.frame, "MainFrame")
SettingsSaveEditorSettings()
DebuggerCloseWatchWindow()
DebuggerKillClient()
if DebuggerCloseWatchWindow then DebuggerCloseWatchWindow() end
if DebuggerCloseStackWindow then DebuggerCloseStackWindow() end
if DebuggerShutdown then DebuggerShutdown() end
ide.settings:delete() -- always delete the config
event:Skip()
-- without explicit exit() the IDE crashes with SIGILL exception when closed
-- on MacOS compiled under 64bit with wxwidgets 2.9.3
if ide.osname == "Macintosh" then os.exit() end
end
frame:Connect(wx.wxEVT_CLOSE_WINDOW, CloseWindow)

View File

@@ -1,5 +1,5 @@
-- Integration with MobDebug
-- Copyright Paul Kulchenko 2011
-- Copyright 2011-12 Paul Kulchenko
-- Original authors: Lomtik Software (J. Winwood & John Labenski)
-- Luxinia Dev (Eike Decker & Christoph Kubisch)
@@ -12,31 +12,132 @@ local debugger = ide.debugger
debugger.server = nil -- DebuggerServer object when debugging, else nil
debugger.running = false -- true when the debuggee is running
debugger.listening = false -- true when the debugger is listening for a client
debugger.portnumber = 8171 -- the port # to use for debugging
debugger.portnumber = mobdebug.port or 8171 -- the port # to use for debugging
debugger.watchWindow = nil -- the watchWindow, nil when not created
debugger.watchListCtrl = nil -- the child listctrl in the watchWindow
debugger.watchCtrl = nil -- the child ctrl in the watchWindow
debugger.stackWindow = nil -- the stackWindow, nil when not created
debugger.stackCtrl = nil -- the child ctrl in the stackWindow
debugger.hostname = (function() -- check what address is resolvable
local addr = wx.wxIPV4address()
for _, host in ipairs({wx.wxGetHostName(), wx.wxGetFullHostName()}) do
if addr:Hostname(host) then return host end
end
return "localhost" -- last resort; no known good hostname
end)()
local notebook = ide.frame.notebook
local function updateWatchesSync()
local watchListCtrl = debugger.watchListCtrl
if watchListCtrl and debugger.server and not debugger.running then
for idx = 0, watchListCtrl:GetItemCount() - 1 do
local expression = watchListCtrl:GetItemText(idx)
local value, _, error = debugger.evaluate(expression)
watchListCtrl:SetItem(idx, 1, value or ('error: ' .. error))
local watchCtrl = debugger.watchCtrl
if watchCtrl and debugger.server
and not debugger.running and not debugger.scratchpad then
for idx = 0, watchCtrl:GetItemCount() - 1 do
local expression = watchCtrl:GetItemText(idx)
local _, values, error = debugger.evaluate(expression)
if error then error = error:gsub("%[.-%]:%d+:%s+","")
elseif #values == 0 then values = {'nil'} end
watchCtrl:SetItem(idx, 1, error and ('error: '..error) or values[1])
end
end
end
local simpleType = {['nil'] = true, ['string'] = true, ['number'] = true, ['boolean'] = true}
local stackItemValue = {}
local function checkIfExpandable(value, item)
local expandable = type(value) == 'table' and next(value) ~= nil
and not stackItemValue[value] -- only expand first time
if expandable then -- cache table value to expand when requested
stackItemValue[item:GetValue()] = value
stackItemValue[value] = item:GetValue() -- to avoid circular refs
end
return expandable
end
local function updateStackSync()
local stackCtrl = debugger.stackCtrl
if stackCtrl and debugger.server
and not debugger.running and not debugger.scratchpad then
local stack = debugger.stack()
if not stack or #stack == 0 then stackCtrl:DeleteAllItems(); return end
stackCtrl:Freeze()
stackCtrl:DeleteAllItems()
local params = {comment = false, nocode = true}
local root = stackCtrl:AddRoot("Stack")
stackItemValue = {} -- reset cache of items in the stack
for _,frame in ipairs(stack) do
-- "main chunk at line 24"
-- "foo() at line 13 (defined at foobar.lua:11)"
-- call = { source.name, source.source, source.linedefined,
-- source.currentline, source.what, source.namewhat, source.short_src }
local call = frame[1]
local func = call[5] == "main" and "main chunk"
or call[5] == "C" and (call[1] or "C function")
or call[5] == "tail" and "tail call"
or (call[1] or "anonymous function")
local text = func ..
(call[4] == -1 and '' or " at line "..call[4]) ..
(call[5] ~= "main" and call[5] ~= "Lua" and ''
or (call[3] > 0 and " (defined at "..call[2]..":"..call[3]..")"
or " (defined in "..call[2]..")"))
local callitem = stackCtrl:AppendItem(root, text, 0)
for name,val in pairs(frame[2]) do
local value, comment = val[1], val[2]
local text = ("%s = %s%s"):
format(name, mobdebug.line(value, params),
simpleType[type(value)] and "" or (" --[["..comment.."]]"))
local item = stackCtrl:AppendItem(callitem, text, 1)
if checkIfExpandable(value, item) then
stackCtrl:SetItemHasChildren(item, true)
end
end
for name,val in pairs(frame[3]) do
local value, comment = val[1], val[2]
local text = ("%s = %s%s"):
format(name, mobdebug.line(value, params),
simpleType[type(value)] and "" or (" --[["..comment.."]]"))
local item = stackCtrl:AppendItem(callitem, text, 2)
if checkIfExpandable(value, item) then
stackCtrl:SetItemHasChildren(item, true)
end
end
stackCtrl:SortChildren(callitem)
stackCtrl:Expand(callitem)
end
stackCtrl:EnsureVisible(stackCtrl:GetFirstChild(root))
stackCtrl:Thaw()
end
end
local function updateStackAndWatches()
if debugger.server and not debugger.running then
copas.addthread(function() updateStackSync() updateWatchesSync() end)
end
end
local function updateWatches()
if debugger.watchListCtrl and debugger.server and not debugger.running then
copas.addthread(updateWatchesSync)
if debugger.server and not debugger.running then
copas.addthread(function() updateWatchesSync() end)
end
end
local function killClient()
if (debugger.pid) then
-- using SIGTERM for some reason kills not only the debugee process,
-- but also some system processes, which leads to a blue screen crash
-- (at least on Windows Vista SP2)
local ret = wx.wxProcess.Kill(debugger.pid, wx.wxSIGKILL, wx.wxKILL_CHILDREN)
if ret == wx.wxKILL_OK then
DisplayOutput(("Program stopped (pid: %d).\n"):format(debugger.pid))
elseif ret ~= wx.wxKILL_NO_PROCESS then
DisplayOutput(("Unable to stop program (pid: %d), code %d.\n")
:format(debugger.pid, ret))
end
debugger.pid = nil
end
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,43 +147,79 @@ 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, "\\", "/")
local filePath = string.gsub(document.filePath, "\\", "/")
local fileName = string.gsub(fileName, "\\", "/")
if string.upper(filePath) == string.upper(fileName) then
local selection = document.index
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)
local function reSetBreakpoints()
-- remove all breakpoints that may still be present from the last session
-- this only matters for those remote clients that reload scripts
-- without resetting their breakpoints
debugger.handle("delallb")
-- go over all windows and find all breakpoints
if (not debugger.scratchpad) then
for _, document in pairs(ide.openDocuments) do
local editor = document.editor
local filePath = document.filePath
local 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
end
debugger.shell = function(expression, isstatement)
if debugger.server and not debugger.running then
copas.addthread(function ()
local addedret = false
local value, _, err = debugger.handle('exec ' .. expression)
if err and (err:find("'=' expected near '<eof>'") or
err:find("unexpected symbol near '")) then
value, _, err = debugger.handle('eval ' .. expression:gsub("^%s*=%s*",""))
addedret = true
-- exec command is not expected to return anything.
-- eval command returns 0 or more results.
-- 'values' has a list of serialized results returned.
-- as it is not possible to distinguish between 0 and nil returned,
-- 'nil' is always returned in this case.
-- the first value returned by eval command is not used;
-- this may need to be taken into account by other debuggers.
local addedret, forceexpression = true, expression:match("^%s*=%s*")
expression = expression:gsub("^%s*=%s*","")
local _, values, err = debugger.evaluate(expression)
if not forceexpression and err and
(err:find("'<eof>' expected near '") or
err:find("'%(' expected near") or
err:find("unexpected symbol near '")) then
_, values, err = debugger.execute(expression)
addedret = false
end
if err then
if addedret then err = err:gsub('^%[string "return ', '[string "') end
DisplayShellErr(err)
elseif addedret or (value ~= nil and value ~= 'nil') then
DisplayShell(value)
elseif addedret or #values > 0 then
-- if empty table is returned, then show nil if this was an expression
if #values == 0 and (forceexpression or not isstatement) then
values = {'nil'}
end
DisplayShell((table.unpack or unpack)(values))
end
end)
end
@@ -90,43 +227,43 @@ end
debugger.listen = function()
local server = socket.bind("*", debugger.portnumber)
DisplayOutput("Started debugger server; clients can connect to "..wx.wxGetHostName()..":"..debugger.portnumber..".\n")
DisplayOutput(("Debugger server started at %s:%d.\n")
:format(debugger.hostname, debugger.portnumber))
copas.autoclose = false
copas.addserver(server, function (skt)
SetAllEditorsReadOnly(true)
if debugger.server then
DisplayOutput("Refused a request to start a new debugging session as there is one in progress already.\n")
return
end
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 or wxfilepath:GetPath(wx.wxPATH_GET_VOLUME)
debugger.basedir = basedir
local basedir = options.basedir
or FileTreeGetDir()
or wxfilepath:GetPath(wx.wxPATH_GET_VOLUME + wx.wxPATH_GET_SEPARATOR)
-- guarantee that the path has a trailing separator
debugger.basedir = wx.wxFileName.DirName(basedir):GetFullPath()
debugger.server = copas.wrap(skt)
debugger.socket = skt
debugger.loop = false
debugger.scratchable = false
debugger.stats = {line = 0}
-- load the remote file into the debugger
-- set basedir first, before loading to make sure that the path is correct
debugger.handle("basedir " .. debugger.basedir)
-- remove all breakpoints that may still be present from the last session
-- this only matters for those remote clients that reload scripts
-- without resetting their breakpoints
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)
end
end
reSetBreakpoints()
if (options.run) then
activateDocument(debugger.handle("run"))
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
@@ -139,36 +276,47 @@ debugger.listen = function()
else
-- try to find a proper file based on file name
-- first check using basedir that was set based on current file path
-- if not found, check using project directory and reset basedir
if not activated then
local fullPath = debugger.basedir..string_Pathsep..file
activated = activateDocument(fullPath, line)
activated = activateDocument(debugger.basedir..file, line)
end
local projectDir = FileTreeGetDir()
if not activated and projectDir then
debugger.basedir = projectDir:gsub(string_Pathsep .. "$", "")
debugger.handle("basedir " .. debugger.basedir)
fullPath = projectDir .. file
activated = activateDocument(fullPath, line)
-- if not found, check using full file path and reset basedir
if not activated then
local path = wxfilepath:GetPath(wx.wxPATH_GET_VOLUME + wx.wxPATH_GET_SEPARATOR)
activated = activateDocument(path..file, line)
if activated then
debugger.basedir = path
debugger.handle("basedir " .. debugger.basedir)
-- reset breakpoints again as basedir has changed
reSetBreakpoints()
end
end
end
if not activated then
DisplayOutput("Can't find file '" .. file .. "' to activate for debugging; try opening the file before debugging.\n")
DisplayOutput(("Can't find file '%s' to activate for debugging; open the file in the editor before debugging.\n")
:format(file))
return debugger.terminate()
end
elseif err then
DisplayOutput(("Can't debug the script in the active editor window. Compilation error:\n%s\n")
:format(err))
return debugger.terminate()
else
debugger.scratchable = true
activateDocument(startfile, 1)
end
end
if (not options.noshell) then
ShellSupportRemote(debugger.shell, debugger.pid)
if (not options.noshell and not debugger.scratchpad) then
ShellSupportRemote(debugger.shell)
end
DisplayOutput("Started remote debugging session (base directory: '" .. debugger.basedir .. "').\n")
updateStackSync()
updateWatchesSync()
DisplayOutput(("Debugging session started in '%s'.\n")
:format(debugger.basedir))
end)
debugger.listening = true
end
@@ -185,7 +333,7 @@ debugger.handle = function(command, server)
end
debugger.running = true
local file, line, err = mobdebug.handle(command, server and server or debugger.server)
local file, line, err = mobdebug.handle(command, server or debugger.server)
debugger.running = false
return file, line, err
@@ -194,25 +342,29 @@ end
debugger.exec = function(command)
if debugger.server and not debugger.running then
copas.addthread(function ()
local out
while true do
local file, line, err = debugger.handle(command)
local file, line, err = debugger.handle(out or command)
if out then out = nil end
if line == nil then
if err then DisplayOutput(err .. "\n") end
DebuggerStop()
return
else
if debugger.basedir and not wx.wxIsAbsolutePath(file) then
file = debugger.basedir .. "/" .. file
file = debugger.basedir .. file
end
if activateDocument(file, line) then
debugger.stats.line = debugger.stats.line + 1
if debugger.loop then
updateStackSync()
updateWatchesSync()
else
updateWatches()
updateStackAndWatches()
return
end
else
command = "out" -- redo now trying to get out of this file
out = "out" -- redo now trying to get out of this file
end
end
end
@@ -220,22 +372,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
killClient()
else -- otherwise, try graceful exit for the remote process
debugger.breaknow("exit")
end
DebuggerStop()
end
@@ -249,28 +404,42 @@ 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.stack = function() return debugger.handle('stack') 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
debugger.quickeval = function(var, callback)
if debugger.server and not debugger.running then
copas.addthread(function ()
local _, values, err = debugger.evaluate(var)
local val = err
and err:gsub("%[.-%]:%d+:%s*","error: ")
or (var .. " = " .. (#values > 0 and values[1] or 'nil'))
if callback then callback(val) end
end)
end
end
----------------------------------------------
@@ -282,19 +451,9 @@ function DebuggerAttachDefault(options)
debugger.listen()
end
function DebuggerKillClient()
if (debugger.pid) then
-- using SIGTERM for some reason kills not only the debugee process,
-- but also some system processes, which leads to a blue screen crash
-- (at least on Windows Vista SP2)
local ret = wx.wxProcess.Kill(debugger.pid, wx.wxSIGKILL, wx.wxKILL_CHILDREN)
if ret == wx.wxKILL_OK then
DisplayOutput("Stopped debuggee process (pid: "..debugger.pid..").\n")
elseif ret ~= wx.wxKILL_NO_PROCESS then
DisplayOutput("Unable to kill debuggee process (pid: "..debugger.pid.."), code "..tostring(ret)..".\n")
end
debugger.pid = nil
end
function DebuggerShutdown()
if debugger.server then debugger.terminate() end
if debugger.pid then killClient() end
end
function DebuggerStop()
@@ -302,33 +461,108 @@ function DebuggerStop()
debugger.server = nil
debugger.pid = nil
SetAllEditorsReadOnly(false)
ShellSupportRemote(nil, 0)
ShellSupportRemote(nil)
ClearAllCurrentLineMarkers()
DisplayOutput("Completed debugging session.\n")
DebuggerScratchpadOff()
DisplayOutput(("Debugging session completed (traced %d instruction%s).\n")
:format(debugger.stats.line, debugger.stats.line == 1 and '' or 's'))
end
end
function DebuggerCreateStackWindow()
DisplayOutput("Not Yet Implemented\n")
end
function DebuggerCloseStackWindow()
if (debugger.stackWindow) then
SettingsSaveFramePosition(debugger.stackWindow, "StackWindow")
debugger.stackCtrl = nil
debugger.stackWindow = nil
end
end
function DebuggerCloseWatchWindow()
if (debugger.watchWindow) then
SettingsSaveFramePosition(debugger.watchWindow, "WatchWindow")
debugger.watchListCtrl = nil
debugger.watchCtrl = nil
debugger.watchWindow = nil
end
end
-- need imglist to be a file local variable as SetImageList takes ownership
-- of it and if done inside a function, icons do not work as expected
local imglist = wx.wxImageList(16,16)
do
local getBitmap = (ide.app.createbitmap or wx.wxArtProvider.GetBitmap)
local size = wx.wxSize(16,16)
-- 0 = stack call
imglist:Add(getBitmap(wx.wxART_GO_FORWARD, wx.wxART_OTHER, size))
-- 1 = local variables
imglist:Add(getBitmap(wx.wxART_LIST_VIEW, wx.wxART_OTHER, size))
-- 2 = upvalues
imglist:Add(getBitmap(wx.wxART_REPORT_VIEW, wx.wxART_OTHER, size))
end
function DebuggerCreateStackWindow()
if (debugger.stackWindow) then return updateStackAndWatches() end
local width = 360
local stackWindow = wx.wxFrame(ide.frame, wx.wxID_ANY,
"Stack Window",
wx.wxDefaultPosition, wx.wxSize(width, 200),
wx.wxDEFAULT_FRAME_STYLE + wx.wxFRAME_FLOAT_ON_PARENT)
debugger.stackWindow = stackWindow
local stackCtrl = wx.wxTreeCtrl(stackWindow, ID "debug.stack",
wx.wxDefaultPosition, wx.wxDefaultSize,
wx.wxTR_LINES_AT_ROOT + wx.wxTR_HAS_BUTTONS + wx.wxTR_SINGLE + wx.wxTR_HIDE_ROOT)
debugger.stackCtrl = stackCtrl
stackCtrl:SetImageList(imglist)
stackWindow:CentreOnParent()
SettingsRestoreFramePosition(stackWindow, "StackWindow")
stackWindow:Show(true)
stackWindow:Connect(wx.wxEVT_CLOSE_WINDOW,
function (event)
DebuggerCloseStackWindow()
stackWindow = nil
stackCtrl = nil
event:Skip()
end)
stackCtrl:Connect( wx.wxEVT_COMMAND_TREE_ITEM_EXPANDING,
function (event)
local item_id = event:GetItem()
local count = stackCtrl:GetChildrenCount(item_id, false)
if count > 0 then return true end
local image = stackCtrl:GetItemImage(item_id)
local num = 1
for name,value in pairs(stackItemValue[item_id:GetValue()]) do
local strval = mobdebug.line(value, {comment = false, nocode = true})
local text = type(name) == "number"
and (num == name and strval or ("[%s] = %s"):format(name, strval))
or ("%s = %s"):format(name, strval)
local item = stackCtrl:AppendItem(item_id, text, image)
if checkIfExpandable(value, item) then
stackCtrl:SetItemHasChildren(item, true)
end
num = num + 1
end
stackCtrl:SortChildren(item_id)
return true
end)
stackCtrl:Connect( wx.wxEVT_COMMAND_TREE_ITEM_COLLAPSED,
function() return true end)
updateStackAndWatches()
end
function DebuggerCreateWatchWindow()
local width = 200
if (debugger.watchWindow) then return updateWatches() end
local width = 360
local watchWindow = wx.wxFrame(ide.frame, wx.wxID_ANY,
"Watch Window",
wx.wxDefaultPosition, wx.wxSize(width, 160),
wx.wxDefaultPosition, wx.wxSize(width, 200),
wx.wxDEFAULT_FRAME_STYLE + wx.wxFRAME_FLOAT_ON_PARENT)
debugger.watchWindow = watchWindow
@@ -343,31 +577,31 @@ function DebuggerCreateWatchWindow()
watchMenuBar:Append(watchMenu, "&Watches")
watchWindow:SetMenuBar(watchMenuBar)
local watchListCtrl = wx.wxListCtrl(watchWindow, ID_WATCH_LISTCTRL,
local watchCtrl = wx.wxListCtrl(watchWindow, ID_WATCH_LISTCTRL,
wx.wxDefaultPosition, wx.wxDefaultSize,
wx.wxLC_REPORT + wx.wxLC_EDIT_LABELS)
debugger.watchListCtrl = watchListCtrl
debugger.watchCtrl = watchCtrl
local info = wx.wxListItem()
info:SetMask(wx.wxLIST_MASK_TEXT + wx.wxLIST_MASK_WIDTH)
info:SetText("Expression")
info:SetWidth(width * 0.45)
watchListCtrl:InsertColumn(0, info)
info:SetWidth(width * 0.32)
watchCtrl:InsertColumn(0, info)
info:SetText("Value")
info:SetWidth(width * 0.45)
watchListCtrl:InsertColumn(1, info)
info:SetWidth(width * 0.56)
watchCtrl:InsertColumn(1, info)
watchWindow:CentreOnParent()
SettingsRestoreFramePosition(watchWindow, "WatchWindow")
watchWindow:Show(true)
local function findSelectedWatchItem()
local count = watchListCtrl:GetSelectedItemCount()
local count = watchCtrl:GetSelectedItemCount()
if count > 0 then
for idx = 0, watchListCtrl:GetItemCount() - 1 do
if watchListCtrl:GetItemState(idx, wx.wxLIST_STATE_FOCUSED) ~= 0 then
for idx = 0, watchCtrl:GetItemCount() - 1 do
if watchCtrl:GetItemState(idx, wx.wxLIST_STATE_FOCUSED) ~= 0 then
return idx
end
end
@@ -375,68 +609,65 @@ function DebuggerCreateWatchWindow()
return -1
end
watchWindow:Connect( wx.wxEVT_CLOSE_WINDOW,
watchWindow:Connect(wx.wxEVT_CLOSE_WINDOW,
function (event)
DebuggerCloseWatchWindow()
watchWindow = nil
watchListCtrl = nil
watchCtrl = nil
event:Skip()
end)
watchWindow:Connect(ID_ADDWATCH, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
local row = watchListCtrl:InsertItem(watchListCtrl:GetItemCount(), "Expr")
watchListCtrl:SetItem(row, 0, "Expr")
watchListCtrl:SetItem(row, 1, "Value")
watchListCtrl:EditLabel(row)
function ()
local row = watchCtrl:InsertItem(watchCtrl:GetItemCount(), "Expr")
watchCtrl:SetItem(row, 0, "Expr")
watchCtrl:SetItem(row, 1, "Value")
watchCtrl:EditLabel(row)
end)
watchWindow:Connect(ID_EDITWATCH, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
function ()
local row = findSelectedWatchItem()
if row >= 0 then
watchListCtrl:EditLabel(row)
watchCtrl:EditLabel(row)
end
end)
watchWindow:Connect(ID_EDITWATCH, wx.wxEVT_UPDATE_UI,
function (event)
event:Enable(watchListCtrl:GetSelectedItemCount() > 0)
event:Enable(watchCtrl:GetSelectedItemCount() > 0)
end)
watchWindow:Connect(ID_REMOVEWATCH, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
function ()
local row = findSelectedWatchItem()
if row >= 0 then
watchListCtrl:DeleteItem(row)
watchCtrl:DeleteItem(row)
end
end)
watchWindow:Connect(ID_REMOVEWATCH, wx.wxEVT_UPDATE_UI,
function (event)
event:Enable(watchListCtrl:GetSelectedItemCount() > 0)
event:Enable(watchCtrl:GetSelectedItemCount() > 0)
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)
event:Enable(watchCtrl:GetItemCount() > 0)
end)
watchListCtrl:Connect(wx.wxEVT_COMMAND_LIST_END_LABEL_EDIT,
watchCtrl:Connect(wx.wxEVT_COMMAND_LIST_END_LABEL_EDIT,
function (event)
watchListCtrl:SetItem(event:GetIndex(), 0, event:GetText())
updateWatches()
if #(event:GetText()) > 0 then
watchCtrl:SetItem(event:GetIndex(), 0, event:GetText())
updateWatches()
end
event:Skip()
end)
end
function DebuggerMakeFileName(editor, filePath)
if not filePath then
filePath = "file"..tostring(editor)
end
return filePath
return filePath or editor:GetText()
end
function DebuggerToggleBreakpoint(editor, line)
@@ -458,3 +689,218 @@ 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
-- take editor text and remove shebang line
local code = scratchpadEditor:GetText():gsub("^#!.-\n", "\n")
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"
-- store if interpreter requires a special handling for external loop
local extloop = ide.interpreter.scratchextloop
local function reloadScratchpadCode()
debugger.scratchpad.running = os.clock()
debugger.scratchpad.updated = false
debugger.scratchpad.runs = (debugger.scratchpad.runs or 0) + 1
-- the code can be running in two ways under scratchpad:
-- 1. controlled by the application, requires stopper (most apps)
-- 2. controlled by some external loop (for example, love2d).
-- in the first case we need to reload the app after each change
-- in the second case, we need to load the app once and then
-- "execute" new code to reflect the changes (with some limitations).
local _, _, err
if extloop then -- if the execution is controlled by an external loop
if debugger.scratchpad.runs == 1
then _, _, err = debugger.loadstring(filePath, code)
else _, _, err = debugger.execute(code) end
else _, _, err = debugger.loadstring(filePath, code .. stopper) end
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) -- disable remote shell
DebuggerRefreshScratchpad()
elseif not ProjectDebug(true, "scratchpad") then
debugger.scratchpad = nil
return
end
local scratchpadEditor = editor
scratchpadEditor:StyleSetUnderline(numberStyle, true)
scratchpadEditor:Connect(wxstc.wxEVT_STC_MODIFIED, function(event)
local evtype = event:GetModificationType()
if (bit.band(evtype,wxstc.wxSTC_MOD_INSERTTEXT) ~= 0 or
bit.band(evtype,wxstc.wxSTC_MOD_DELETETEXT) ~= 0 or
bit.band(evtype,wxstc.wxSTC_PERFORMED_UNDO) ~= 0 or
bit.band(evtype,wxstc.wxSTC_PERFORMED_REDO) ~= 0) then
debugger.scratchpad.updated = true
end
event:Skip()
end)
scratchpadEditor:Connect(wx.wxEVT_LEFT_DOWN, function(event)
local scratchpad = debugger.scratchpad
local point = event:GetPosition()
local pos = scratchpadEditor:PositionFromPoint(point)
-- are we over a number in the scratchpad? if not, it's not our event
if ((not scratchpad) or
(bit.band(scratchpadEditor:GetStyleAt(pos),31) ~= numberStyle)) then
event:Skip()
return
end
-- find start position and length of the number
local text = scratchpadEditor:GetText()
local nstart = pos
while nstart >= 0
and (bit.band(scratchpadEditor:GetStyleAt(nstart),31) == numberStyle)
do nstart = nstart - 1 end
local nend = pos
while nend < string.len(text)
and (bit.band(scratchpadEditor:GetStyleAt(nend),31) == numberStyle)
do nend = nend + 1 end
-- check if there is minus sign right before the number and include it
if nstart >= 0 and scratchpadEditor:GetTextRange(nstart,nstart+1) == '-' then
nstart = nstart - 1
end
scratchpad.start = nstart + 1
scratchpad.length = nend - nstart - 1
scratchpad.origin = tonumber(scratchpadEditor:GetTextRange(nstart+1,nend))
if scratchpad.origin then
scratchpad.point = point
scratchpadEditor:CaptureMouse()
end
end)
scratchpadEditor:Connect(wx.wxEVT_LEFT_UP, function(event)
if debugger.scratchpad and debugger.scratchpad.point then
debugger.scratchpad.point = nil
debugger.scratchpad.editor:ReleaseMouse()
wx.wxSetCursor(wx.wxNullCursor) -- restore cursor
else event:Skip() end
end)
scratchpadEditor:Connect(wx.wxEVT_MOTION, function(event)
local point = event:GetPosition()
local pos = scratchpadEditor:PositionFromPoint(point)
local scratchpad = debugger.scratchpad
local ipoint = scratchpad and scratchpad.point
-- record the fact that we are over a number or dragging slider
scratchpad.over = scratchpad and
(ipoint or (bit.band(scratchpadEditor:GetStyleAt(pos),31) == numberStyle))
if ipoint then
-- calculate difference in point position
local dx = point.x - ipoint.x
local dy = - (point.y - ipoint.y) -- invert dy as y increases down
-- re-calculate the value
local startpos = scratchpad.start
local endpos = scratchpad.start+scratchpad.length
local num = tonumber(scratchpad.origin) + dx/10
-- update length
scratchpad.length = string.len(num)
-- update the value in the document
scratchpadEditor:SetTargetStart(startpos)
scratchpadEditor:SetTargetEnd(endpos)
scratchpadEditor:ReplaceTarget("" .. num)
else event:Skip() end
end)
scratchpadEditor:Connect(wx.wxEVT_SET_CURSOR, function(event)
if (debugger.scratchpad and debugger.scratchpad.over) then
event:SetCursor(wx.wxCursor(wx.wxCURSOR_SIZEWE))
else event:Skip() end
end)
return true
end
function DebuggerScratchpadOff()
if not debugger.scratchpad then return end
local scratchpadEditor = debugger.scratchpad.editor
scratchpadEditor:StyleSetUnderline(numberStyle, false)
scratchpadEditor:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wxstc.wxEVT_STC_MODIFIED)
scratchpadEditor:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wx.wxEVT_MOTION)
scratchpadEditor:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wx.wxEVT_LEFT_DOWN)
scratchpadEditor:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wx.wxEVT_LEFT_UP)
scratchpadEditor:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wx.wxEVT_SET_CURSOR)
debugger.scratchpad = nil
debugger.terminate()
-- disable menu if it is still enabled
-- (as this may be called when the debugger is being shut down)
local menuBar = ide.frame.menuBar
if menuBar:IsChecked(ID_RUNNOW) then menuBar:Check(ID_RUNNOW, false) end
return true
end

View File

@@ -3,11 +3,9 @@
---------------------------------------------------------
local wxkeywords = nil -- a string of the keywords for scintilla of wxLua's wx.XXX items
local in_evt_focus = false -- true when in editor focus event to avoid recursion
local editorID = 100 -- window id to create editor pages with, incremented for new editors
local openDocuments = ide.openDocuments
local ignoredFilesList = ide.ignoredFilesList
local statusBar = ide.frame.statusBar
local notebook = ide.frame.notebook
local funclist = ide.frame.toolBar.funclist
@@ -17,10 +15,10 @@ 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())
funclist:SetFont(ide.font.fNormal)
local function updateStatusText(editor)
local texts = { "", "", "" }
@@ -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)
@@ -102,7 +105,7 @@ local function isFileAlteredOnDisk(editor)
wx.wxMessageBox(fileName.." is no longer on the disk.",
GetIDEString("editormessage"),
wx.wxOK + wx.wxCENTRE, ide.frame)
elseif modTime:IsValid() and oldModTime:IsEarlierThan(modTime) then
elseif not editor:GetReadOnly() and modTime:IsValid() and oldModTime:IsEarlierThan(modTime) then
local ret = wx.wxMessageBox(fileName.." has been modified on disk.\nDo you want to reload it?",
GetIDEString("editormessage"),
wx.wxYES_NO + wx.wxCENTRE, ide.frame)
@@ -118,11 +121,12 @@ end
-- ----------------------------------------------------------------------------
-- Get/Set notebook editor page, use nil for current page, returns nil if none
function GetEditor(selection)
local editor = nil
if selection == nil then
selection = notebook:GetSelection()
end
if (selection >= 0) and (selection < notebook:GetPageCount()) and (notebook:GetPage(selection):GetClassInfo():GetClassName()=="wxStyledTextCtrl") then
local editor
if (selection >= 0) and (selection < notebook:GetPageCount())
and (notebook:GetPage(selection):GetClassInfo():GetClassName()=="wxStyledTextCtrl") then
editor = notebook:GetPage(selection):DynamicCast("wxStyledTextCtrl")
end
return editor
@@ -143,7 +147,6 @@ function SetEditorSelection(selection)
editor:SetFocus()
editor:SetSTCFocus(true)
isFileAlteredOnDisk(editor)
local id = editor:GetId()
if openDocuments[id] and openDocuments[id].filePath then
FileTreeMarkSelected(openDocuments[id].filePath)
@@ -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
@@ -203,8 +206,8 @@ function CreateEditor(name)
editor:SetBufferedDraw(true)
editor:StyleClearAll()
editor:SetFont(ide.font)
editor:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, ide.font)
editor:SetFont(ide.font.eNormal)
editor:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, ide.font.eNormal)
editor:SetTabWidth(ide.config.editor.tabwidth or 4)
editor:SetIndent(ide.config.editor.tabwidth or 4)
@@ -214,10 +217,8 @@ function CreateEditor(name)
if (ide.config.editor.usewrap) then
editor:SetWrapMode(wxstc.wxSTC_WRAP_WORD)
editor:SetWrapStartIndent(2)
editor:SetWrapStartIndent(0)
editor:SetWrapVisualFlagsLocation(wxstc.wxSTC_WRAPVISUALFLAGLOC_END_BY_TEXT)
editor:SetWrapVisualFlags(wxstc.wxSTC_WRAPVISUALFLAG_START)
editor:WrapCount(80)
end
editor:SetCaretLineVisible(ide.config.editor.caretline and 1 or 0)
@@ -305,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)
@@ -321,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
@@ -363,42 +363,70 @@ function CreateEditor(name)
end)
editor:Connect(wxstc.wxEVT_STC_SAVEPOINTREACHED,
function (event)
function ()
SetDocumentModified(editor:GetId(), false)
end)
editor:Connect(wxstc.wxEVT_STC_SAVEPOINTLEFT,
function (event)
function ()
SetDocumentModified(editor:GetId(), true)
end)
editor:Connect(wxstc.wxEVT_STC_UPDATEUI,
function (event)
function ()
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]+1) end
end
if MarkupStyleRefresh then MarkupStyleRefresh(editor, editor.ev) end
editor.ev = {}
end)
editor:Connect(wx.wxEVT_LEFT_DOWN,
function (event)
if MarkupHotspotClick then
local position = editor:PositionFromPointClose(event:GetX(),event:GetY())
if position ~= wx.wxSTC_INVALID_POSITION then
if MarkupHotspotClick(position, editor) then return end
end
end
event:Skip()
end)
local inhandler = false
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 inhandler or ide.exitingProgram then return end
inhandler = true
isFileAlteredOnDisk(editor)
ide.in_evt_focus = false
inhandler = false
end)
--[[
editor:Connect(wxstc.wxEVT_STC_POSCHANGED,
editor:Connect(wx.wxEVT_KEY_DOWN,
function (event)
-- brace checking
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 = {}
@@ -420,10 +448,10 @@ function GetSpec(ext,forcespec)
-- search proper spec
-- allow forcespec for "override"
if ext and not spec then
for i,curspec in pairs(ide.specs) do
exts = curspec.exts
for _,curspec in pairs(ide.specs) do
local exts = curspec.exts
if (exts) then
for n,curext in ipairs(exts) do
for _,curext in ipairs(exts) do
if (curext == ext) then
spec = curspec
break
@@ -441,24 +469,17 @@ end
function IndicateFunctions(editor, lines, linee)
if (not (edcfg.showfncall and editor.spec and editor.spec.isfncall)) then return end
--DisplayOutput("indicate: "..tostring(lines).." "..tostring(linee).."\n")
local es = editor:GetEndStyled()
local lines = lines or 0
local linee = linee or editor:GetLineCount()-1
if (lines < 0) then return end
local isfunc = editor.spec.isfncall
local iscomment = editor.spec.iscomment
local iskeyword0 = editor.spec.iskeyword0
local isfncall = editor.spec.isfncall
local isinvalid = {}
for i,v in pairs(iscomment) do
isinvalid[i] = v
end
for i,v in pairs(iskeyword0) do
isinvalid[i] = v
end
for i,v in pairs(editor.spec.iscomment) do isinvalid[i] = v end
for i,v in pairs(editor.spec.iskeyword0) do isinvalid[i] = v end
for i,v in pairs(editor.spec.isstring) do isinvalid[i] = v end
local INDICS_MASK = wxstc.wxSTC_INDICS_MASK
local INDIC0_MASK = wxstc.wxSTC_INDIC0_MASK
@@ -475,20 +496,13 @@ function IndicateFunctions(editor, lines, linee)
while from do
tx = from==1 and tx or string.sub(tx,from)
local f,t,w = isfunc(tx)
local f,t,w = isfncall(tx)
if (f) then
local p = ls+f+off
local s = bit.band(editor:GetStyleAt(p),31)
if (not (isinvalid[s])) then
editor:StartStyling(p,INDICS_MASK)
editor:SetStyling(t-f,INDIC0_MASK + 1)
else
editor:StartStyling(p,INDICS_MASK)
editor:SetStyling(t-f,0)
end
editor:StartStyling(p,INDICS_MASK)
editor:SetStyling(#w,isinvalid[s] and 0 or (INDIC0_MASK + 1))
off = off + t
end
from = t and (t+1)
@@ -500,7 +514,6 @@ end
function SetupKeywords(editor, ext, forcespec, styles, font, fontitalic)
local lexerstyleconvert = nil
local spec = forcespec or GetSpec(ext)
--print(ext..":"..tostring(spec.apitype))
-- found a spec setup lexers and keywords
if spec then
editor:SetLexer(spec.lexer or wxstc.wxSTC_LEX_NULL)
@@ -516,11 +529,11 @@ function SetupKeywords(editor, ext, forcespec, styles, font, fontitalic)
-- Get the items in the global "wx" table for autocompletion
if not wxkeywords then
local keyword_table = {}
for index, value in pairs(wx) do
for index in pairs(wx) do
table.insert(keyword_table, "wx."..index.." ")
end
for index, value in pairs(wxstc) do
for index in pairs(wxstc) do
table.insert(keyword_table, "wxstc."..index.." ")
end
@@ -542,7 +555,7 @@ function SetupKeywords(editor, ext, forcespec, styles, font, fontitalic)
end
StylesApplyToEditor(styles or ide.config.styles, editor,
font or ide.font,fontitalic or ide.fontItalic,lexerstyleconvert)
font or ide.font.eNormal,fontitalic or ide.font.eItalic,lexerstyleconvert)
end
----------------------------------------------------
@@ -570,9 +583,13 @@ funclist:Connect(wx.wxEVT_SET_FOCUS,
local linee = editor:GetLineCount()-1
for line=lines,linee do
local tx = editor:GetLine(line)
local s,e,cap,l = editor.spec.isfndef(tx)
local s,_,cap,l = editor.spec.isfndef(tx)
if (s) then
funclist:Append((l and " " or "")..cap,line)
local ls = editor:PositionFromLine(line)
local style = bit.band(editor:GetStyleAt(ls+s),31)
if not (editor.spec.iscomment[style] or editor.spec.isstring[style]) then
funclist:Append((l and " " or "")..cap,line)
end
end
end

View File

@@ -23,25 +23,22 @@ ide.filetree = {
root_id = nil,
rootdir = "",
},
imglist,
newfiledir,
}
local filetree = ide.filetree
local frame = ide.frame
-- generic tree
-- ------------
do
local getBitmap = (ide.app.createbitmap or wx.wxArtProvider.GetBitmap)
local size = wx.wxSize(16, 16)
filetree.imglist = wx.wxImageList(16,16)
-- 0 = directory
filetree.imglist:Add(wx.wxArtProvider.GetBitmap(wx.wxART_FOLDER, wx.wxART_OTHER, size))
filetree.imglist:Add(getBitmap(wx.wxART_FOLDER, wx.wxART_OTHER, size))
-- 1 = file known spec
filetree.imglist:Add(wx.wxArtProvider.GetBitmap(wx.wxART_HELP_PAGE, wx.wxART_OTHER, size))
filetree.imglist:Add(getBitmap(wx.wxART_HELP_PAGE, wx.wxART_OTHER, size))
-- 2 = file rest
filetree.imglist:Add(wx.wxArtProvider.GetBitmap(wx.wxART_NORMAL_FILE, wx.wxART_OTHER, size))
filetree.imglist:Add(getBitmap(wx.wxART_NORMAL_FILE, wx.wxART_OTHER, size))
end
local function treeAddDir(tree,parent_id,rootdir)
@@ -54,11 +51,10 @@ local function treeAddDir(tree,parent_id,rootdir)
end
local curr
local search = rootdir..string_Pathsep.."*.*"
local dirs = FileSysGet(search,wx.wxDIR)
local search = rootdir..string_Pathsep.."*"
-- append directories
for i,dir in ipairs(dirs) do
for _,dir in ipairs(FileSysGet(search,wx.wxDIR)) do
local name = dir:match("%"..string_Pathsep.."("..stringset_File.."+)$")
local icon = 0
local item = items[name .. icon]
@@ -82,10 +78,9 @@ local function treeAddDir(tree,parent_id,rootdir)
end
-- then append files
local files = FileSysGet(search,wx.wxFILE)
for i,file in ipairs(files) do
for _,file in ipairs(FileSysGet(search,wx.wxFILE)) 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
@@ -131,10 +126,8 @@ end
local function treeSetRoot(tree,treedata,rootdir)
tree:DeleteAllItems()
if (not wx.wxDirExists(rootdir)) then
treedata.root_id = nil
tree:AddRoot("Invalid")
return
end
@@ -160,10 +153,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 +175,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
@@ -221,7 +196,8 @@ local projtree = wx.wxTreeCtrl(projpanel, ID "filetree.projtree",
or (wx.wxTR_HAS_BUTTONS + wx.wxTR_SINGLE + wx.wxTR_HIDE_ROOT))
-- use the same font in the combobox as is used in the filetree
projcombobox:SetFont(projtree:GetFont())
projtree:SetFont(ide.font.fNormal)
projcombobox:SetFont(ide.font.fNormal)
local projTopSizer = wx.wxBoxSizer( wx.wxHORIZONTAL );
projTopSizer:Add(projcombobox, 1, wx.wxALL + wx.wxALIGN_LEFT + wx.wxGROW, 0)
@@ -287,8 +263,8 @@ projpanel.projcombobox = projcombobox
projpanel.projtree = projtree
function FileTreeGetDir()
-- atm only projtree
return projpanel:IsShown() and (filetree.newfiledir .. string_Pathsep)
return projpanel:IsShown() and filetree.newfiledir
and wx.wxFileName.DirName(filetree.newfiledir):GetFullPath()
end
function FileTreeSetProjects(tab)
@@ -306,7 +282,7 @@ local function findItem(tree, match)
local node = projtree:GetRootItem()
local label = tree:GetItemText(node)
local s, e = string.find(match, label .. string_Pathsep)
local s, e = string.find(match, label)
if not s or s ~= 1 then return end
for token in string.gmatch(string.sub(match,e+1), "[^%"..string_Pathsep.."]+") do
@@ -330,7 +306,7 @@ end
local curr_id
function FileTreeMarkSelected(file)
if not file then return end
if not file or not filetree.projdirText or #filetree.projdirText == 0 then return end
local item_id = findItem(projtree, file)
if curr_id ~= item_id then
if curr_id and projtree:IsBold(curr_id) then
@@ -343,3 +319,7 @@ function FileTreeMarkSelected(file)
end
end
end
function FileTreeRefresh()
treeSetRoot(projtree,filetree.projdata,filetree.projdirText)
end

View File

@@ -219,9 +219,8 @@ end
local function ProcInFiles(startdir,mask,subdirs,replace)
if (subdirs) then
local dirs = FileSysGet(startdir..string_Pathsep.."*.*",wx.wxDIR)
local dirs = FileSysGet(startdir..string_Pathsep.."*",wx.wxDIR)
for i,dir in ipairs(dirs) do
--DisplayOutput(dir.."\n")
ProcInFiles(dir,mask,true,replace)
end
end
@@ -230,8 +229,6 @@ local function ProcInFiles(startdir,mask,subdirs,replace)
for i,file in ipairs(files) do
findReplace.curfilename = file
--DisplayOutput(file.."\n")
-- load file
local handle = io.open(file, "rb")
if handle then

View File

@@ -10,33 +10,23 @@ BREAKPOINT_MARKER_VALUE = 2 -- = 2^BREAKPOINT_MARKER
CURRENT_LINE_MARKER = 2
CURRENT_LINE_MARKER_VALUE = 4 -- = 2^CURRENT_LINE_MARKER
-- Globals
local font = nil -- fonts to use for the editor
local fontItalic = nil
local ofont = nil -- fonts to use for the outputshell
local ofontItalic = nil
-- ----------------------------------------------------------------------------
-- Pick some reasonable fixed width fonts to use for the editor
if wx.__WXMSW__ then
font = wx.wxFont(ide.config.editor.fontsize or 10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_NORMAL, wx.wxFONTWEIGHT_NORMAL, false, ide.config.editor.fontname or "Courier New")
fontItalic = wx.wxFont(ide.config.editor.fontsize or 10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_ITALIC, wx.wxFONTWEIGHT_NORMAL, false, ide.config.editor.fontname or "Courier New")
else
font = wx.wxFont(ide.config.editor.fontsize or 10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_NORMAL, wx.wxFONTWEIGHT_NORMAL, false, ide.config.editor.fontname or "")
fontItalic = wx.wxFont(ide.config.editor.fontsize or 10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_ITALIC, wx.wxFONTWEIGHT_NORMAL, false, ide.config.editor.fontname or "")
local function setFont(style, config, name, size)
return wx.wxFont(config.fontsize or size or 10, wx.wxFONTFAMILY_MODERN, style,
wx.wxFONTWEIGHT_NORMAL, false, config.fontname or name,
config.fontencoding or wx.wxFONTENCODING_DEFAULT)
end
ide.font.eNormal = setFont(wx.wxFONTSTYLE_NORMAL, ide.config.editor, wx.__WXMSW__ and "Courier New" or "")
ide.font.eItalic = setFont(wx.wxFONTSTYLE_ITALIC, ide.config.editor, wx.__WXMSW__ and "Courier New" or "")
if wx.__WXMSW__ then
ofont = wx.wxFont(ide.config.outputshell.fontsize or 10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_NORMAL, wx.wxFONTWEIGHT_NORMAL, false, ide.config.outputshell.fontname or "Courier New")
ofontItalic = wx.wxFont(ide.config.outputshell.fontsize or 10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_ITALIC, wx.wxFONTWEIGHT_NORMAL, false, ide.config.outputshell.fontname or "Courier New")
else
ofont = wx.wxFont(ide.config.outputshell.fontsize or 10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_NORMAL, wx.wxFONTWEIGHT_NORMAL, false, ide.config.outputshell.fontname or "")
ofontItalic = wx.wxFont(ide.config.outputshell.fontsize or 10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_ITALIC, wx.wxFONTWEIGHT_NORMAL, false, ide.config.outputshell.fontname or "")
end
ide.font.oNormal = setFont(wx.wxFONTSTYLE_NORMAL, ide.config.outputshell, wx.__WXMSW__ and "Courier New" or "")
ide.font.oItalic = setFont(wx.wxFONTSTYLE_ITALIC, ide.config.outputshell, wx.__WXMSW__ and "Courier New" or "")
ide.font = font
ide.fontItalic = fontItalic
ide.ofont = ofont
ide.ofontItalic = ofontItalic
-- treeCtrl font requires slightly different handling
local gui, config = wx.wxTreeCtrl():GetFont(), ide.config.filetree
if config.fontsize then gui:SetPointSize(config.fontsize) end
if config.fontname then gui:SetFaceName(config.fontname) end
ide.font.fNormal = gui
-- ----------------------------------------------------------------------------
-- Create the wxFrame
@@ -44,7 +34,10 @@ ide.ofontItalic = ofontItalic
local function createFrame()
frame = wx.wxFrame(wx.NULL, wx.wxID_ANY, GetIDEString("editor"),
wx.wxDefaultPosition, wx.wxSize(1000, 700))
frame:DragAcceptFiles(true)
-- wrap into protected call as DragAcceptFiles fails on MacOS with
-- wxwidgets 2.8.12 even though it should work according to change notes
-- for 2.8.10: "Implemented wxWindow::DragAcceptFiles() on all platforms."
pcall(function() frame:DragAcceptFiles(true) end)
frame:Connect(wx.wxEVT_DROP_FILES,function(evt)
local files = evt:GetFiles()
if not files or #files == 0 then return end
@@ -54,7 +47,6 @@ local function createFrame()
end)
local menuBar = wx.wxMenuBar()
frame:SetMenuBar(menuBar)
frame.menuBar = menuBar
local statusBar = frame:CreateStatusBar( 5 )
@@ -76,23 +68,24 @@ local function createToolBar(frame)
local funclist = wx.wxChoice.new(toolBar,ID "toolBar.funclist",wx.wxDefaultPosition, wx.wxSize.new(240,16))
-- note: Ususally the bmp size isn't necessary, but the HELP icon is not the right size in MSW
local getBitmap = (ide.app.createbitmap or wx.wxArtProvider.GetBitmap)
local toolBmpSize = toolBar:GetToolBitmapSize()
toolBar:AddTool(ID_NEW, "New", wx.wxArtProvider.GetBitmap(wx.wxART_NORMAL_FILE, wx.wxART_TOOLBAR, toolBmpSize), "Create an empty document")
toolBar:AddTool(ID_OPEN, "Open", wx.wxArtProvider.GetBitmap(wx.wxART_FILE_OPEN, wx.wxART_TOOLBAR, toolBmpSize), "Open an existing document")
toolBar:AddTool(ID_SAVE, "Save", wx.wxArtProvider.GetBitmap(wx.wxART_FILE_SAVE, wx.wxART_TOOLBAR, toolBmpSize), "Save the current document")
toolBar:AddTool(ID_SAVEALL, "Save All", wx.wxArtProvider.GetBitmap(wx.wxART_NEW_DIR, wx.wxART_TOOLBAR, toolBmpSize), "Save all documents")
toolBar:AddTool(ID_NEW, "New", getBitmap(wx.wxART_NORMAL_FILE, wx.wxART_TOOLBAR, toolBmpSize), "Create an empty document")
toolBar:AddTool(ID_OPEN, "Open", getBitmap(wx.wxART_FILE_OPEN, wx.wxART_TOOLBAR, toolBmpSize), "Open an existing document")
toolBar:AddTool(ID_SAVE, "Save", getBitmap(wx.wxART_FILE_SAVE, wx.wxART_TOOLBAR, toolBmpSize), "Save the current document")
toolBar:AddTool(ID_SAVEALL, "Save All", getBitmap(wx.wxART_NEW_DIR, wx.wxART_TOOLBAR, toolBmpSize), "Save all documents")
toolBar:AddSeparator()
toolBar:AddTool(ID_CUT, "Cut", wx.wxArtProvider.GetBitmap(wx.wxART_CUT, wx.wxART_TOOLBAR, toolBmpSize), "Cut the selection")
toolBar:AddTool(ID_COPY, "Copy", wx.wxArtProvider.GetBitmap(wx.wxART_COPY, wx.wxART_TOOLBAR, toolBmpSize), "Copy the selection")
toolBar:AddTool(ID_PASTE, "Paste", wx.wxArtProvider.GetBitmap(wx.wxART_PASTE, wx.wxART_TOOLBAR, toolBmpSize), "Paste text from the clipboard")
toolBar:AddTool(ID_CUT, "Cut", getBitmap(wx.wxART_CUT, wx.wxART_TOOLBAR, toolBmpSize), "Cut the selection")
toolBar:AddTool(ID_COPY, "Copy", getBitmap(wx.wxART_COPY, wx.wxART_TOOLBAR, toolBmpSize), "Copy the selection")
toolBar:AddTool(ID_PASTE, "Paste", getBitmap(wx.wxART_PASTE, wx.wxART_TOOLBAR, toolBmpSize), "Paste text from the clipboard")
toolBar:AddSeparator()
toolBar:AddTool(ID_UNDO, "Undo", wx.wxArtProvider.GetBitmap(wx.wxART_UNDO, wx.wxART_TOOLBAR, toolBmpSize), "Undo last edit")
toolBar:AddTool(ID_REDO, "Redo", wx.wxArtProvider.GetBitmap(wx.wxART_REDO, wx.wxART_TOOLBAR, toolBmpSize), "Redo last undo")
toolBar:AddTool(ID_UNDO, "Undo", getBitmap(wx.wxART_UNDO, wx.wxART_TOOLBAR, toolBmpSize), "Undo last edit")
toolBar:AddTool(ID_REDO, "Redo", getBitmap(wx.wxART_REDO, wx.wxART_TOOLBAR, toolBmpSize), "Redo last undo")
toolBar:AddSeparator()
toolBar:AddTool(ID_FIND, "Find", wx.wxArtProvider.GetBitmap(wx.wxART_FIND, wx.wxART_TOOLBAR, toolBmpSize), "Find text")
toolBar:AddTool(ID_REPLACE, "Replace", wx.wxArtProvider.GetBitmap(wx.wxART_FIND_AND_REPLACE, wx.wxART_TOOLBAR, toolBmpSize), "Find and replace text")
toolBar:AddTool(ID_FIND, "Find", getBitmap(wx.wxART_FIND, wx.wxART_TOOLBAR, toolBmpSize), "Find text")
toolBar:AddTool(ID_REPLACE, "Replace", getBitmap(wx.wxART_FIND_AND_REPLACE, wx.wxART_TOOLBAR, toolBmpSize), "Find and replace text")
toolBar:AddSeparator()
toolBar:AddTool(ID "debug.projectdir.fromfile", "Update", wx.wxArtProvider.GetBitmap(wx.wxART_GO_DIR_UP , wx.wxART_TOOLBAR, toolBmpSize), "Sets projectdir from file")
toolBar:AddTool(ID "debug.projectdir.fromfile", "Update", getBitmap(wx.wxART_GO_DIR_UP , wx.wxART_TOOLBAR, toolBmpSize), "Sets projectdir from file")
toolBar:AddSeparator()
toolBar:AddControl(funclist)
toolBar:Realize()
@@ -110,26 +103,27 @@ local function createNotebook(frame)
wxaui.wxAUI_NB_DEFAULT_STYLE + wxaui.wxAUI_NB_TAB_EXTERNAL_MOVE
+ wx.wxNO_BORDER)
-- the following group of event handlers allows the active editor
-- to get/keep focus after execution of Run and other commands
local current -- the currently active editor, needed by the focus selection
local function onPageChange(event)
current = event:GetSelection() -- update the active editor reference
SetEditorSelection(current)
event:Skip() -- skip to let page change
end
notebook:Connect(wx.wxEVT_SET_FOCUS, -- Notepad tabs shouldn't be selectable,
function (event)
SetEditorSelection(current) -- select the currently active editor
end)
notebook:Connect(wx.wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED, onPageChange)
notebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED, onPageChange)
notebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE,
function (event)
ClosePage(event:GetSelection())
event:Veto() -- don't propagate the event as the page is already closed
end)
notebook:Connect(wx.wxEVT_SET_FOCUS, -- Notepad tabs shouldn't be selectable,
function (event)
SetEditorSelection(current) -- select the currently active editor
end)
frame.notebook = notebook
return notebook
end

View File

@@ -37,14 +37,13 @@ ID_FIND_IN_FILES = NewID()
ID_REPLACE_IN_FILES = NewID()
ID_GOTOLINE = NewID()
ID_SORT = NewID()
-- Debug menu
-- Project/Debug menu
ID_TOGGLEBREAKPOINT = NewID()
ID_COMPILE = NewID()
ID_RUN = NewID()
ID_RUNNOW = NewID()
ID_ATTACH_DEBUG = NewID()
ID_START_DEBUG = NewID()
--ID_USECONSOLE = NewID()
ID_STOP_DEBUG = NewID()
ID_STEP = NewID()
ID_STEP_OVER = NewID()
@@ -52,10 +51,12 @@ ID_STEP_OUT = NewID()
ID_CONTINUE = NewID()
ID_BREAK = NewID()
ID_TRACE = NewID()
--ID_VIEWCALLSTACK = NewID()
--ID_VIEWWATCHWINDOW = NewID()
ID_VIEWCALLSTACK = NewID()
ID_VIEWWATCHWINDOW = NewID()
ID_FULLSCREEN = NewID()
ID_CLEAROUTPUT = NewID()
ID_DEBUGGER_PORT = NewID()
ID_PROJECTDIR = NewID()
ID_INTERPRETER = NewID()
-- Help menu
ID_ABOUT = wx.wxID_ABOUT
-- Watch window menu items
@@ -65,8 +66,6 @@ ID_EDITWATCH = NewID()
ID_REMOVEWATCH = NewID()
ID_EVALUATEWATCH = NewID()
ID_WORKDIR_CHOSE = NewID()
local ids = {}
function ID (name)
ids[name] = ids[name] or NewID()

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

@@ -0,0 +1,180 @@
-- Integration with LuaInspect
-- (C) 2012 Paul Kulchenko
local M, LA, LI, T = {}
local FAST = true
local function init()
if LA then return end
require "metalua"
LA = require "luainspect.ast"
LI = require "luainspect.init"
T = require "luainspect.types"
if FAST then
LI.eval_comments = function () end
LI.infer_values = function () end
end
end
function M.warnings_from_string(src, file)
init()
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 '?'
local name = ast[1]
-- check if we're masking a variable in the same scope
if ast.localmasking and name ~= '_' and
ast.level == ast.localmasking.level then
local linenum = ast.localmasking.lineinfo.first[1]
local parent = ast.parent and ast.parent.parent
local func = parent and parent.tag == 'Localrec'
warn("local " .. (func and 'function' or 'variable') .. " '" ..
name .. "' 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 name ~= 'self' then
local func = parent.parent and parent.parent.parent
local assignment = not func.tag or func.tag == 'Set' or func.tag == 'Localrec'
local fname = assignment and 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 '" .. name .. "'" ..
(func and assignment
and (fname and func.tag
and (" in function '" .. fname .. "'")
or " in anonymous function")
or ""),
line, path)
end
else
if parent.tag == 'Localrec' then -- local function foo...
warn("unused local function '" .. name .. "'", line, path)
else
warn("unused local variable '" .. name .. "'; "..
"consider removing or replacing with '_'", line, path)
end
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 " .. name, ast.lineinfo.first[1], path)
elseif ast.tag == 'Id' and not ast.localdefinition and not ast.definedglobal then
if not globseen[name] then
globseen[name] = true
local parent = ast.parent
-- if being called and not one of the parameters
if parent and parent.tag == 'Call' and parent[1] == ast then
warn("first use of unknown global function '" .. name .. "'", line, path)
else
warn("first use of unknown global variable '" .. name .. "'", line, path)
end
end
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[name] -- report assignments to global
-- only report if it is on the left side of the assignment
-- this is a bit tricky as it can be assigned as part of a, b = c, d
-- `Set{ {lhs+} {expr+} } -- lhs1, lhs2... = e1, e2...
and parent[1] == ast.parent
and parent[2][1].tag ~= "Function" then -- but ignore global functions
warn("first assignment to global variable '" .. name .. "'", line, path)
globseen[name] = true
end
elseif (ast.tag == 'Set' or ast.tag == 'Local') and #(ast[2]) > #(ast[1]) then
warn(("value discarded in multiple assignment: %d values assigned to %d variable%s")
:format(#(ast[2]), #(ast[1]), #(ast[1]) > 1 and 's' or ''), line, path)
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 '" .. name .. "': " .. 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((": %s warning%s.\n")
:format(#warn > 0 and #warn or 'no', #warn == 1 and '' or 's'))
DisplayOutputNoMarker(table.concat(warn, "\n") .. "\n")
return true -- analyzed ok
end
frame:Connect(ID_ANALYZE, wx.wxEVT_COMMAND_MENU_SELECTED,
function ()
ActivateOutput()
local editor = GetEditor()
if not analyzeProgram(editor) then CompileProgram(editor) end
end)
frame:Connect(ID_ANALYZE, wx.wxEVT_UPDATE_UI,
function (event)
local editor = GetEditor()
event:Enable((debugger.server == nil and debugger.pid == nil) and (editor ~= nil))
end)

View File

@@ -39,7 +39,7 @@ ide.iofilters["GermanUtf8Ascii"] = {
}
local lst = "["
for k in pairs(charconv) do lst = lst .. k end
lst = "]"
lst = lst.."]"
return content:gsub(lst,charconv)
end,

202
src/editor/markup.lua Normal file
View File

@@ -0,0 +1,202 @@
-- Copyright (C) Paul Kulchenko 2011-2012
-- 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 description start
local MD_MARK_LINZ = ']' -- link description end
local MD_MARK_LINA = '(' -- link URL start
local MD_MARK_LINT = ')' -- link URL end
local MD_MARK_HEAD = '#' -- header
local MD_MARK_CODE = '`' -- code
local MD_MARK_BOXD = '|' -- highlight
local MD_MARK_MARK = ' ' -- separator
local MD_LINK_NEWWINDOW = '+' -- indicator to open a new window for links
local markup = {
[MD_MARK_BOXD] = {st=25, fg={127,0,127}, bg=comment.bg, b=true},
[MD_MARK_CODE] = {st=26, fg={127,127,127}, bg=comment.bg, fs=9},
[MD_MARK_HEAD] = {st=27, fg=comment.fg, bg=comment.bg, fn="Lucida Console", b=true},
[MD_MARK_LINK] = {st=28, fg=comment.fg, bg=comment.bg, u=true, hs={0, 0, 127}},
[MD_MARK_BOLD] = {st=29, fg=comment.fg, bg=comment.bg, b=true, fs=11},
[MD_MARK_ITAL] = {st=30, fg=comment.fg, bg=comment.bg, i=true},
[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 .. q(key) end
end
function MarkupHotspotClick(pos, editor)
-- check if this is "our" hotspot event
if bit.band(editor:GetStyleAt(pos),31) ~= markup[MD_MARK_LINK].st then
-- not "our" style, so nothing to do for us here
return
end
local line = editor:LineFromPosition(pos)
local tx = editor:GetLine(line)
pos = pos + #MD_MARK_LINK - editor:PositionFromLine(line) -- turn into relative position
-- extract the URL/command on the right side of the separator
local _,_,text = string.find(tx, q(MD_MARK_LINZ).."(%b"..MD_MARK_LINA..MD_MARK_LINT..")", pos)
if text then
text = text:gsub("^"..q(MD_MARK_LINA), ""):gsub(q(MD_MARK_LINT).."$", "")
local filepath = ide.openDocuments[editor:GetId()].filePath
local _,_,shell = string.find(text, [[^macro:shell%((.*%S)%)$]])
local _,_,http = string.find(text, [[^(https?:%S+)$]])
local _,_,command = string.find(text, [[^macro:(%w+)$]])
if shell then
ShellExecuteCode(shell)
elseif command == 'run' then -- run the current file
ProjectRun()
elseif command == 'debug' then -- debug the current file
ProjectDebug()
elseif http then -- open the URL in a new browser window
wx.wxLaunchDefaultBrowser(http, 0)
elseif filepath then -- only check for saved files
-- check if requested to open in a new window
local newwindow = string.find(text, MD_LINK_NEWWINDOW, 1, true) -- plain search
if newwindow then text = string.gsub(text, "^%" .. MD_LINK_NEWWINDOW, "") end
local name = wx.wxFileName(filepath):GetPath(wx.wxPATH_GET_VOLUME
+ wx.wxPATH_GET_SEPARATOR) .. text
-- load/activate file
local filename = wx.wxFileName(name)
filename:Normalize() -- remove .., ., and other similar elements
if filename:FileExists() and
(newwindow or SaveModifiedDialog(editor, true) ~= wx.wxID_CANCEL) then
if not newwindow and ide.osname == 'Macintosh' then editor:GotoPos(0) end
LoadFile(filename,not newwindow and editor or nil,true)
end
end
end
return true
end
local function ismarkup (tx)
local start = 1
while true do
-- find a separator first
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
-- 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
-- allow everything based on balanced link separators
s,e,cap = string.find(tx,
"^(%b"..MD_MARK_LINK..MD_MARK_LINZ
.."%b"..MD_MARK_LINA..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 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
function MarkupStyle(editor, lines, linee)
local lines = lines or 0
if (lines < 0) then return end
-- 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()
local iscomment = {}
for i,v in pairs(editor.spec.iscomment) do
iscomment[i] = v
end
for line=lines,linee do
local tx = editor:GetLine(line)
local ls = editor:PositionFromLine(line)
editor:StartStyling(ls, 0)
local from = 1
local off = -1
while from do
tx = string.sub(tx,from)
local f,t,w,mark = ismarkup(tx)
if (f) then
local p = ls+f+off
local s = bit.band(editor:GetStyleAt(p), 31)
-- only style comments and only those that are not at the beginning
-- of the file to avoid styling shebang (#!) lines
if iscomment[s] and p > 0 then
local smark = #mark
local emark = #mark -- assumes end mark is the same length as start mark
if mark == MD_MARK_HEAD then
-- 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 lsep = w:find(q(MD_MARK_LINZ)..q(MD_MARK_LINA))
if lsep then emark = #w-lsep+#MD_MARK_LINT end
end
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
end
from = t and (t+1)
end
end
end
-- this could work by calling MarkupStyle directly from EVT_UPDATEUI,
-- but the styling didn't work correctly as the style on block comments
-- (which is used to identify where the markup should be applied)
-- was not always correct during UPDATEUI event.
-- to rectify this, we style immediately (by calling MarkupStyle
-- from UPDATEUI), but also store the starting point and re-style during
-- the next UPDATEUI/IDLE event when the block comment style is correct.
local needStyle = {}
local frame
function MarkupStyleRefresh(editor, ev)
if not frame then
frame = ide.frame
frame:Connect(wx.wxEVT_IDLE,
function(event) MarkupStyleRefresh(); event:Skip() end)
end
if not ev or #ev == 0 then -- no new records, refresh deferred ones
for ed,line in pairs(needStyle) do
MarkupStyle(ed, line)
needStyle[ed] = nil
end
else -- store records from the event table to defer refresh
for _,pos in ipairs(ev) do
needStyle[editor] = editor:LineFromPosition(pos[1])
end
end
end

View File

@@ -8,5 +8,3 @@ dofile "src/editor/menu_search.lua"
dofile "src/editor/menu_view.lua"
dofile "src/editor/menu_project.lua"
dofile "src/editor/menu_tools.lua"
ide.frame:SetMenuBar(ide.frame.menuBar )

View File

@@ -17,11 +17,11 @@ local editMenu = wx.wxMenu{
{ ID_UNDO, "&Undo\tCtrl-Z", "Undo the last action" },
{ ID_REDO, "&Redo\tCtrl-Y", "Redo the last action undone" },
{ },
{ ID "edit.showtooltip", "Show &Tooltip\tCtrl+T", "Show tooltip for current position. Place cursor after opening bracket of function."},
{ ID_AUTOCOMPLETE, "Complete &Identifier\tCtrl+K", "Complete the current identifier" },
{ ID "edit.showtooltip", "Show &Tooltip\tCtrl-T", "Show tooltip for current position. Place cursor after opening bracket of function."},
{ ID_AUTOCOMPLETE, "Complete &Identifier\tCtrl-K", "Complete the current identifier" },
{ ID_AUTOCOMPLETE_ENABLE, "Auto complete Identifiers", "Auto complete while typing", wx.wxITEM_CHECK },
{ },
{ ID_COMMENT, "C&omment/Uncomment\tCtrl-Q", "Comment or uncomment current or selected lines"},
{ ID_COMMENT, "C&omment/Uncomment\tCtrl-U", "Comment or uncomment current or selected lines"},
{ },
{ ID_FOLD, "&Fold/Unfold all\tF12", "Fold or unfold all code folds"},
{ ID "edit.cleardynamics", "Clear &Dynamic Words", "Resets the dynamic word list for autcompletion."},
@@ -35,13 +35,13 @@ function OnUpdateUIEditMenu(event) -- enable if there is a valid focused editor
event:Enable(editor ~= nil)
end
local shellboxeditor = ide.frame.bottomnotebook.shellbox
local othereditors = { frame.bottomnotebook.shellbox, frame.bottomnotebook.errorlog }
function OnEditMenu(event)
local menu_id = event:GetId()
local editor = GetEditor()
if shellboxeditor:FindFocus():GetId() == shellboxeditor:GetId() then
editor = shellboxeditor
for _,e in pairs(othereditors) do
if e:FindFocus():GetId() == e:GetId() then editor = e end
end
if editor == nil then return end
@@ -93,22 +93,38 @@ frame:Connect(ID "edit.cleardynamics", wx.wxEVT_COMMAND_MENU_SELECTED,
frame:Connect(ID "edit.showtooltip", wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
local editor = GetEditor()
if (editor:CallTipActive()) then
editor:CallTipCancel()
return
end
local pos = editor:GetCurrentPos()
local line = editor:GetCurrentLine()
local linetx = editor:GetLine(line)
local linestart = editor:PositionFromLine(line)
local localpos = pos-linestart
linetxtopos = linetx:sub(1,localpos)
local ident = "([a-zA-Z_0-9][a-zA-Z_0-9%.%:]*)"
local linetxtopos = linetx:sub(1,localpos)
linetxtopos = linetxtopos..")"
linetxtopos = linetxtopos:match("([a-zA-Z_0-9%.%:]+)%b()$")
linetxtopos = linetxtopos:match(ident .. "%b()$")
local tip = linetxtopos and GetTipInfo(editor,linetxtopos.."(",false)
if tip then
if(editor:CallTipActive()) then
editor:CallTipCancel()
editor:CallTipShow(pos, tip)
else
-- check if we have a selected text or an identifier
-- for an identifier, check fragments on the left and on the right.
-- this is to match 'io' in 'i^o.print' and 'io.print' in 'io.pr^int'
local left = linetx:sub(1,localpos):match(ident.."$")
local right = linetx:sub(localpos+1,#linetx):match("^[a-zA-Z_0-9]*")
local var = editor:GetSelectionStart() ~= editor:GetSelectionEnd()
and editor:GetSelectedText()
or left and left..right or nil
if var and ide.debugger then
ide.debugger.quickeval(var, function(val) editor:CallTipShow(pos, val) end)
end
editor:CallTipShow(pos,tip)
end
end)

View File

@@ -7,20 +7,19 @@ local ide = ide
local frame = ide.frame
local menuBar = frame.menuBar
local openDocuments = ide.openDocuments
local debugger = ide.debugger
local fileMenu = wx.wxMenu({
{ ID_NEW, "&New\tCtrl-N", "Create an empty document" },
{ ID_OPEN, "&Open...\tCtrl-O", "Open an existing document" },
{ ID_CLOSE, "&Close page\tCtrl+W", "Close the current editor window" },
{ ID_CLOSE, "&Close page\tCtrl-W", "Close the current editor window" },
{ },
{ ID_SAVE, "&Save\tCtrl-S", "Save the current document" },
{ ID_SAVEAS, "Save &As...\tAlt-Shift-S", "Save the current document to a file with a new name" },
{ ID_SAVEALL, "Save A&ll...\tCtrl-Shift-S", "Save all open documents" },
{ ID_SAVEALL, "Save A&ll...", "Save all open documents" },
{ },
--{ ID "file.recentfiles", "Recent files",},
{ },
{ ID_EXIT, "E&xit\tAlt-X", "Exit Program" }})
{ ID_EXIT, "E&xit", "Exit Program" }})
menuBar:Append(fileMenu, "&File")
local filehistorymenu = wx.wxMenu({})
@@ -99,15 +98,17 @@ frame:Connect(ID_SAVEALL, wx.wxEVT_UPDATE_UI,
event:Enable(atLeastOneModifiedDocument)
end)
frame:Connect(ID_CLOSE, wx.wxEVT_COMMAND_MENU_SELECTED, ClosePage)
frame:Connect(ID_CLOSE, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
ClosePage() -- this will find the current editor
end)
frame:Connect(ID_CLOSE, wx.wxEVT_UPDATE_UI,
function (event)
event:Enable((GetEditor() ~= nil) and (debugger.server == nil))
event:Enable(GetEditor() ~= nil)
end)
frame:Connect(ID_EXIT, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
if not SaveOnExit(true) then return end
frame:Close() -- will handle wxEVT_CLOSE_WINDOW
DebuggerCloseWatchWindow()
frame:Close() -- this will trigger wxEVT_CLOSE_WINDOW
end)

View File

@@ -11,6 +11,8 @@ local menuBar = frame.menuBar
local openDocuments = ide.openDocuments
local debugger = ide.debugger
local filetree = ide.filetree
local bottomnotebook = frame.bottomnotebook
local uimgr = frame.uimgr
------------------------
-- Interpreters and Menu
@@ -39,28 +41,28 @@ do
targetMenu = wx.wxMenu(targetargs)
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" },
{ ID_ATTACH_DEBUG, "&Start Debugger Server", "Allow a client to start a debugging session" },
{ },
{ ID_STOP_DEBUG, "S&top Debugging\tShift-F12", "Stop and end the debugging session" },
{ ID_STOP_DEBUG, "S&top Debugging\tShift-F12", "Stop the currently running process" },
{ ID_STEP, "St&ep\tF11", "Step into the next line" },
{ ID_STEP_OVER, "Step &Over\tF10", "Step over the next line" },
{ ID_STEP_OUT, "Step O&ut\tShift-F10", "Step out of the current function" },
{ ID_TRACE, "Tr&ace", "Trace execution showing each executed line" },
{ ID_BREAK, "&Break", "Stop execution of the program at the next executed line of code" },
{ ID_BREAK, "&Break\tShift-F9", "Stop execution of the program at the next executed line of code" },
{ },
{ ID_TOGGLEBREAKPOINT, "Toggle &Breakpoint\tF9", "Toggle Breakpoint" },
--{ ID "view.debug.callstack", "V&iew Call Stack", "View the LUA call stack" },
{ ID_TOGGLEBREAKPOINT, "Toggle Break&point\tF9", "Toggle Breakpoint" },
{ },
{ ID_CLEAROUTPUT, "C&lear Output Window", "Clear the output window before compiling or debugging", wx.wxITEM_CHECK },
}
local debugMenu = wx.wxMenu(debugTab)
local debugMenuRun = {start="Start &Debugging\tF5", continue="Co&ntinue\tF5"}
local debugMenuStop = {debugging="S&top Debugging\tShift-F12", process="S&top Process\tShift-F12"}
local targetDirMenu = wx.wxMenu{
{ID "debug.projectdir.choose","Choose ..."},
@@ -69,8 +71,8 @@ local targetDirMenu = wx.wxMenu{
{ID "debug.projectdir.currentdir",""}
}
debugMenu:Append(0,"Lua &interpreter",targetMenu,"Set the interpreter to be used")
debugMenu:Append(0,"Project directory",targetDirMenu,"Set the project directory to be used")
debugMenu:Append(ID_INTERPRETER,"Lua &Interpreter",targetMenu,"Set the interpreter to be used")
debugMenu:Append(ID_PROJECTDIR,"Project Directory",targetDirMenu,"Set the project directory to be used")
menuBar:Append(debugMenu, "&Project")
-----------------------------
@@ -159,14 +161,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
@@ -177,11 +179,53 @@ local function getNameToRun()
return wx.wxFileName(openDocuments[id].filePath)
end
function ActivateOutput()
if not ide.config.activateoutput then return end
-- show output/errorlog pane
uimgr:GetPane("bottomnotebook"):Show(true)
uimgr:Update()
-- activate output/errorlog window
local index = bottomnotebook:GetPageIndex(bottomnotebook.errorlog)
if bottomnotebook:GetSelection() ~= index then
bottomnotebook:SetSelection(index)
end
end
local function runInterpreter(wfilename, withdebugger)
ActivateOutput()
ClearAllCurrentLineMarkers()
if not wfilename then return end
local pid = ide.interpreter:frun(wfilename, withdebugger)
if withdebugger then debugger.pid = pid end
debugger.pid = ide.interpreter:frun(wfilename, withdebugger)
return debugger.pid
end
function ProjectRun(skipcheck)
local fname = getNameToRun(skipcheck)
if not fname then return end
runInterpreter(fname)
return true
end
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
local debcall = (debuggers[debtype or "debug"]):
format(ide.debugger.hostname, ide.debugger.portnumber)
local fname = getNameToRun(skipcheck)
if not fname then return end
runInterpreter(fname, debcall)
end
return true
end
-----------------------
@@ -202,22 +246,38 @@ frame:Connect(ID_TOGGLEBREAKPOINT, wx.wxEVT_UPDATE_UI,
frame:Connect(ID_COMPILE, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
local editor = GetEditor()
ActivateOutput()
CompileProgram(editor)
end)
frame:Connect(ID_COMPILE, wx.wxEVT_UPDATE_UI,
function (event)
local editor = GetEditor()
event:Enable((debugger.server == nil) and (editor ~= nil))
event:Enable((debugger.server == nil and debugger.pid == nil) and (editor ~= nil))
end)
frame:Connect(ID_RUN, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
runInterpreter(getNameToRun())
end)
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 (editor ~= nil))
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 and it is
-- allowed to turn it into a scratchpad) and we are not debugging anything
event:Enable((ide.interpreter) and (ide.interpreter.hasdebugger) and
(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,
@@ -228,46 +288,40 @@ 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)
local lastcontinue
frame:Connect(ID_START_DEBUG, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
if (debugger.server ~= nil) then
if (not debugger.running) then
ClearAllCurrentLineMarkers()
debugger.run()
end
else
runInterpreter(getNameToRun(), true)
end
end)
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) or (debugger.server ~= nil and not debugger.running)) and (editor ~= nil))
local curcontinue = (debugger.server ~= nil)
if curcontinue == lastcontinue then return end
lastcontinue = curcontinue
if curcontinue then
debugMenu:SetLabel(ID_START_DEBUG, debugMenuRun.continue)
else
debugMenu:SetLabel(ID_START_DEBUG, debugMenuRun.start)
((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
debugMenu:SetLabel(ID_START_DEBUG, label)
end
end)
frame:Connect(ID_STOP_DEBUG, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
ClearAllCurrentLineMarkers()
debugger.terminate()
DebuggerShutdown()
end)
frame:Connect(ID_STOP_DEBUG, wx.wxEVT_UPDATE_UI,
function (event)
local editor = GetEditor()
event:Enable((debugger.server ~= nil) and (editor ~= nil))
event:Enable((debugger.server ~= nil or debugger.pid ~= nil) and (editor ~= nil))
local label = (debugger.server == nil and debugger.pid ~= nil)
and debugMenuStop.process or debugMenuStop.debugging
if debugMenu:GetLabel(ID_STOP_DEBUG) ~= label then
debugMenu:SetLabel(ID_STOP_DEBUG, label)
end
end)
frame:Connect(ID_STEP, wx.wxEVT_COMMAND_MENU_SELECTED,
@@ -278,7 +332,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,
@@ -289,7 +344,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,
@@ -300,7 +356,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,
@@ -311,7 +368,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,
@@ -323,25 +381,13 @@ 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)
--[[
frame:Connect(ID "view.debug.callstack", wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
if debugger.server then
DebuggerCreateStackWindow()
end
end)
frame:Connect(ID "view.debug.callstack", wx.wxEVT_UPDATE_UI,
function (event)
event:Enable((debugger.server ~= nil) and (not debugger.running))
end)
]]
frame:Connect(wx.wxEVT_IDLE,
function(event)
if (debugger.update) then
debugger.update()
end
if (debugger.update) then debugger.update() end
if (debugger.scratchpad) then DebuggerRefreshScratchpad() end
event:Skip() -- let other EVT_IDLE handlers to work on the event
end)

View File

@@ -13,10 +13,10 @@ local findMenu = wx.wxMenu{
{ ID_FIND, "&Find\tCtrl-F", "Find the specified text" },
{ ID_FINDNEXT, "Find &Next\tF3", "Find the next occurrence of the specified text" },
{ ID_FINDPREV, "Find &Previous\tShift-F3", "Repeat the search backwards in the file" },
{ ID_REPLACE, "&Replace\tCtrl-H", "Replaces the specified text with different text" },
{ ID_REPLACE, "&Replace\tCtrl-R", "Replaces the specified text with different text" },
{ },
{ ID_FIND_IN_FILES, "Find &In Files\tCtrl-Shift-F", " Find specified text in files"},
{ ID_REPLACE_IN_FILES, "Re&place In Files\tCtrl-Shift-H", " Replace specified text in files"},
{ ID_REPLACE_IN_FILES, "Re&place In Files\tCtrl-Shift-R", " Replace specified text in files"},
{ },
{ ID_GOTOLINE, "&Goto line\tCtrl-G", "Go to a selected line" },
{ },

View File

@@ -6,16 +6,16 @@ local frame = ide.frame
local menuBar = frame.menuBar
local uimgr = frame.uimgr
local debugger = ide.debugger
local viewMenu = wx.wxMenu{
local viewMenu = wx.wxMenu {
-- NYI { ID "view.preferences", "&Preferences...", "Brings up dialog for settings (TODO)" },
-- NYI { },
{ ID "view.filetree.show", "Project/&FileTree Window", "View the project/filetree window" },
{ ID "view.output.show", "&Output/Shell Window", "View the output/shell window" },
{ ID "view.debug.watches", "&Watch Window", "View the Watch window" },
{ ID "view.filetree.show", "Project/&FileTree Window\tCtrl-Shift-P", "View the project/filetree window" },
{ ID "view.output.show", "&Output/Console Window\tCtrl-Shift-O", "View the output/console window" },
{ ID_VIEWWATCHWINDOW, "&Watch Window\tCtrl-Shift-W", "View the Watch window" },
{ ID_VIEWCALLSTACK, "&Stack Window\tCtrl-Shift-S", "View the Stack window" },
{ },
{ ID "view.defaultlayout", "&Default Layout", "Reset to default ui layout"},
{ ID "view.defaultlayout", "&Default Layout", "Reset to default layout"},
{ ID_FULLSCREEN, "Full &Screen\tCtrl-Shift-A", "Switch to or from full screen mode"},
{ ID "view.style.loadconfig", "&Load Config Style...", "Load and apply style from config file (must contain .styles)"},
}
menuBar:Append(viewMenu, "&View")
@@ -45,9 +45,12 @@ frame:Connect(ID "view.filetree.show", wx.wxEVT_COMMAND_MENU_SELECTED,
uimgr:Update()
end)
frame:Connect(ID "view.debug.watches", wx.wxEVT_COMMAND_MENU_SELECTED,
function (event)
if not debugger.watchWindow then
DebuggerCreateWatchWindow()
end
frame:Connect(ID_FULLSCREEN, wx.wxEVT_COMMAND_MENU_SELECTED, function ()
pcall(function() ShowFullScreen(not frame:IsFullScreen()) end)
end)
frame:Connect(ID_VIEWWATCHWINDOW, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event) DebuggerCreateWatchWindow() end)
frame:Connect(ID_VIEWCALLSTACK, wx.wxEVT_COMMAND_MENU_SELECTED,
function (event) DebuggerCreateStackWindow() end)

View File

@@ -9,17 +9,22 @@ local errorlog = bottomnotebook.errorlog
-------
-- setup errorlog
local INPUT_MARKER = 3
local INPUT_MARKER_VALUE = 2^INPUT_MARKER
errorlog:Show(true)
errorlog:SetFont(ide.ofont)
errorlog:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, ide.ofont)
errorlog:SetFont(ide.font.oNormal)
errorlog:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, ide.font.oNormal)
errorlog:StyleClearAll()
errorlog:SetMarginWidth(1, 16) -- marker margin
errorlog:SetMarginType(1, wxstc.wxSTC_MARGIN_SYMBOL);
errorlog:MarkerDefine(CURRENT_LINE_MARKER, wxstc.wxSTC_MARK_ARROWS, wx.wxBLACK, wx.wxWHITE)
errorlog:MarkerDefine(INPUT_MARKER, wxstc.wxSTC_MARK_CHARACTER+string.byte('>'),
wx.wxColour(127, 127, 127), wx.wxColour(240, 240, 240))
errorlog:SetReadOnly(true)
StylesApplyToEditor(ide.config.stylesoutshell,errorlog,ide.ofont,ide.ofontItalic)
StylesApplyToEditor(ide.config.stylesoutshell,errorlog,ide.font.oNormal,ide.font.oItalic)
function ClearOutput(event)
function ClearOutput()
errorlog:SetReadOnly(false)
errorlog:ClearAll()
errorlog:SetReadOnly(true)
@@ -33,9 +38,11 @@ function DisplayOutputNoMarker(...)
message = message..tostring(v)..(i<cnt and "\t" or "")
end
local current = errorlog:GetReadOnly()
errorlog:SetReadOnly(false)
errorlog:AppendText(message)
errorlog:SetReadOnly(true)
errorlog:EmptyUndoBuffer()
errorlog:SetReadOnly(current)
errorlog:GotoPos(errorlog:GetLength())
end
function DisplayOutput(...)
@@ -45,7 +52,9 @@ end
local streamins = {}
local streamerrs = {}
local streamouts = {}
local customprocs = {}
local textout = '' -- this is a buffer for any text sent to external scripts
function CommandLineRunning(uid)
for pid,custom in pairs(customprocs) do
@@ -71,7 +80,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,37 +92,39 @@ 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
end
local function nameTab(tab, name)
local index = bottomnotebook:GetPageIndex(tab)
if index then bottomnotebook:SetPageText(index, name) end
end
function CommandLineRun(cmd,wdir,tooutput,nohide,stringcallback,uid,endcallback)
if (not cmd) then return end
-- try to extract the name of the executable from the command
-- the executable may not have the extension and may be in quotes
local exename = string.gsub(cmd, "\\", "/")
exename = string.match(exename,'%/*([^%/]+%.%w+)') or exename
exename = string.match(exename,'%/*([^%/]+%.%w+)[%s%"]') or exename
local _,_,fullname = string.find(exename,'^[\'"]([^\'"]+)[\'"]')
exename = fullname and string.match(fullname,'/?([^/]+)$')
or string.match(exename,'/?([^/]-)%s') or exename
uid = uid or exename
if (CommandLineRunning(uid)) then
DisplayOutput("Conflicting Process still running: "..cmd.."\n")
DisplayOutput(("Program can't start because conflicting process is running as '%s'.\n")
:format(cmd))
return
end
DisplayOutput("Running program: "..cmd.."\n")
DisplayOutput(("Program starting as '%s'.\n"):format(cmd))
local pid = -1
local proc = nil
local customproc
if (tooutput) then
customproc = wx.wxProcess(errorlog)
customproc:Redirect()
proc = customproc
end
local proc = wx.wxProcess(errorlog)
if (tooutput) then proc:Redirect() end -- redirect the output if requested
-- manipulate working directory
local oldcwd
@@ -121,42 +134,64 @@ function CommandLineRun(cmd,wdir,tooutput,nohide,stringcallback,uid,endcallback)
end
-- launch process
local pid = (proc and wx.wxExecute(cmd, wx.wxEXEC_ASYNC + (nohide and wx.wxEXEC_NOHIDE or 0),proc) or
wx.wxExecute(cmd, wx.wxEXEC_ASYNC + (nohide and wx.wxEXEC_NOHIDE or 0)))
local params = wx.wxEXEC_ASYNC + wx.wxEXEC_MAKE_GROUP_LEADER + (nohide and wx.wxEXEC_NOHIDE or 0)
local pid = wx.wxExecute(cmd, params, proc)
if (oldcwd) then
wx.wxFileName.SetCwd(oldcwd)
end
-- check process
if not pid or pid == -1 then
DisplayOutputNoMarker("Unknown ERROR Running program!\n")
customproc = nil
-- For asynchronous execution, the return value is the process id and
-- zero value indicates that the command could not be executed.
-- The return value of -1 in this case indicates that we didn't launch
-- a new process, but connected to the running one (e.g. DDE under Windows).
if not pid or pid == -1 or pid == 0 then
DisplayOutput(("Program unable to run as '%s'\n"):format(cmd))
return
else
DisplayOutput(
"Process: "..uid..", pid:"..tostring(pid)..
", started in '"..(wdir and wdir or wx.wxFileName.GetCwd()).."'\n")
customprocs[pid] = {proc=customproc, uid=uid, endcallback=endcallback}
end
DisplayOutput(("Program '%s' started in '%s' (pid: %d).\n")
:format(uid, (wdir and wdir or wx.wxFileName.GetCwd()), pid))
customprocs[pid] = {proc=proc, uid=uid, endcallback=endcallback, started = os.clock()}
local streamin = proc and proc:GetInputStream()
local streamerr = proc and proc:GetErrorStream()
local streamout = proc and proc:GetOutputStream()
if (streamin) then
streamins[pid] = {stream=streamin, callback=stringcallback}
end
if (streamerr) then
streamerrs[pid] = {stream=streamerr, callback=stringcallback}
end
if (streamout) then
streamouts[pid] = {stream=streamout, callback=stringcallback, out=true}
end
unHideWxWindow(pid)
nameTab(errorlog, "Output (running)")
return pid
end
local inputBound -- to track where partial output ends for input editing purposes
local function getInputLine()
local totalLines = errorlog:GetLineCount()
return errorlog:MarkerPrevious(totalLines+1, INPUT_MARKER_VALUE)
end
local function getInputText(bound)
return errorlog:GetTextRange(
errorlog:PositionFromLine(getInputLine())+(bound or 0), errorlog:GetLength())
end
local function updateInputMarker()
local lastline = errorlog:GetLineCount()-1
errorlog:MarkerDeleteAll(INPUT_MARKER)
errorlog:MarkerAdd(lastline, INPUT_MARKER)
inputBound = #getInputText()
end
local function getStreams()
local function displayStream(tab)
for i,v in pairs(tab) do
local function readStream(tab)
for _,v in pairs(tab) do
while(v.stream:CanRead()) do
local str = v.stream:Read(4096)
local pfn
@@ -168,34 +203,66 @@ local function getStreams()
else
DisplayOutputNoMarker(str)
end
if str and ide.config.allowinteractivescript and
(getInputLine() > -1 or errorlog:GetReadOnly()) then
ActivateOutput()
updateInputMarker()
end
pfn = pfn and pfn()
end
end
end
local function sendStream(tab)
local str = textout
if not str then return end
textout = nil
str = str .. "\n"
for _,v in pairs(tab) do
local pfn
if (v.callback) then
str,pfn = v.callback(str)
end
v.stream:Write(str, #str)
updateInputMarker()
pfn = pfn and pfn()
end
end
displayStream(streamins)
displayStream(streamerrs)
readStream(streamins)
readStream(streamerrs)
sendStream(streamouts)
end
errorlog:Connect(wx.wxEVT_END_PROCESS, function(event)
local pid = event:GetPid()
if (pid ~= -1) then
getStreams()
-- delete markers and set focus to the editor if there is an input marker
if errorlog:MarkerPrevious(errorlog:GetLineCount(), INPUT_MARKER_VALUE) > -1 then
errorlog:MarkerDeleteAll(INPUT_MARKER)
local editor = GetEditor()
-- check if editor still exists; it may not if the window is closed
if editor then editor:SetFocus() end
end
nameTab(errorlog, "Output")
local runtime = os.clock() - customprocs[pid].started
streamins[pid] = nil
streamerrs[pid] = nil
streamouts[pid] = nil
if (customprocs[pid].endcallback) then
customprocs[pid].endcallback()
end
customprocs[pid] = nil
unHideWxWindow(0)
DisplayOutput("Program finished (pid: "..pid..").\n")
DebuggerStop()
DisplayOutput(("Program completed in %.2f seconds (pid: %d).\n")
:format(runtime, pid))
end
end)
errorlog:Connect(wx.wxEVT_IDLE, function(event)
if (#streamins or #streamerrs) then
getStreams()
end
errorlog:Connect(wx.wxEVT_IDLE, function()
if (#streamins or #streamerrs) then getStreams() end
unHideWxWindow()
end)
@@ -211,7 +278,7 @@ local jumptopatterns = {
}
errorlog:Connect(wxstc.wxEVT_STC_DOUBLECLICK,
function(event)
function()
local line = errorlog:GetCurrentLine()
local linetx = errorlog:GetLine(line)
-- try to detect a filename + line
@@ -221,7 +288,7 @@ errorlog:Connect(wxstc.wxEVT_STC_DOUBLECLICK,
local jumpline
local jumplinepos
for i,pattern in ipairs(jumptopatterns) do
for _,pattern in ipairs(jumptopatterns) do
fname,jumpline,jumplinepos = linetx:match(pattern)
if (fname and jumpline) then
break
@@ -240,5 +307,92 @@ errorlog:Connect(wxstc.wxEVT_STC_DOUBLECLICK,
editor:SetFocus()
end
end
end)
local function positionInLine(line)
return errorlog:GetCurrentPos() - errorlog:PositionFromLine(line)
end
local function caretOnInputLine(disallowLeftmost)
local inputLine = getInputLine()
local boundary = inputBound + (disallowLeftmost and 0 or -1)
return (errorlog:GetCurrentLine() > inputLine
or errorlog:GetCurrentLine() == inputLine
and positionInLine(inputLine) > boundary)
end
errorlog:Connect(wx.wxEVT_KEY_DOWN,
function (event)
-- this loop is only needed to allow to get to the end of function easily
-- "return" aborts the processing and ignores the key
-- "break" aborts the processing and processes the key normally
while true do
-- no special processing if it's readonly
if errorlog:GetReadOnly() then break end
local key = event:GetKeyCode()
if key == wx.WXK_UP or key == wx.WXK_NUMPAD_UP then
if errorlog:GetCurrentLine() > getInputLine() then break
else return end
elseif key == wx.WXK_DOWN or key == wx.WXK_NUMPAD_DOWN then
break -- can go down
elseif key == wx.WXK_LEFT or key == wx.WXK_NUMPAD_LEFT then
if not caretOnInputLine(true) then return end
elseif key == wx.WXK_BACK then
if not caretOnInputLine(true) then return end
elseif key == wx.WXK_DELETE or key == wx.WXK_NUMPAD_DELETE then
if not caretOnInputLine()
or errorlog:LineFromPosition(errorlog:GetSelectionStart()) < getInputLine() then
return
end
elseif key == wx.WXK_PAGEUP or key == wx.WXK_NUMPAD_PAGEUP
or key == wx.WXK_PAGEDOWN or key == wx.WXK_NUMPAD_PAGEDOWN
or key == wx.WXK_END or key == wx.WXK_NUMPAD_END
or key == wx.WXK_HOME or key == wx.WXK_NUMPAD_HOME
or key == wx.WXK_RIGHT or key == wx.WXK_NUMPAD_RIGHT
or key == wx.WXK_SHIFT or key == wx.WXK_CONTROL
or key == wx.WXK_ALT then
break
elseif key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER then
if not caretOnInputLine()
or errorlog:LineFromPosition(errorlog:GetSelectionStart()) < getInputLine() then
return
end
errorlog:GotoPos(errorlog:GetLength()) -- move to the end
textout = (textout or '') .. getInputText(inputBound)
-- remove selection if any, otherwise the text gets replaced
errorlog:SetSelection(errorlog:GetSelectionEnd()+1,errorlog:GetSelectionEnd())
break -- don't need to do anything else with return
else
-- move cursor to end if not already there
if not caretOnInputLine() then
errorlog:GotoPos(errorlog:GetLength())
-- check if the selection starts before the input line and reset it
elseif errorlog:LineFromPosition(errorlog:GetSelectionStart()) < getInputLine(-1) then
errorlog:GotoPos(errorlog:GetLength())
errorlog:SetSelection(errorlog:GetSelectionEnd()+1,errorlog:GetSelectionEnd())
end
end
break
end
event:Skip()
end)
local function inputEditable(line)
local inputLine = getInputLine()
local currentLine = line or errorlog:GetCurrentLine()
return inputLine > -1 and
(currentLine > inputLine or
currentLine == inputLine and positionInLine(inputLine) >= inputBound) and
not (errorlog:LineFromPosition(errorlog:GetSelectionStart()) < getInputLine())
end
errorlog:Connect(wxstc.wxEVT_STC_UPDATEUI,
function () errorlog:SetReadOnly(not inputEditable()) end)
-- only allow copy/move text by dropping to the input line
errorlog:Connect(wxstc.wxEVT_STC_DO_DROP,
function (event)
if not inputEditable(errorlog:LineFromPosition(event:GetPosition())) then
event:SetDragResult(wx.wxDragNone)
end
end)

View File

@@ -233,7 +233,6 @@ end
-----------------------------------
local function saveNotebook(nb)
local cnt = nb:GetPageCount()
@@ -250,7 +249,6 @@ local function saveNotebook(nb)
for i=1,cnt do
local id = nb:GetPageText(i-1)
local pg = nb:GetPage(i-1)
local x,y = pg:GetPosition():GetXY()
addTo(pagesX,x,id)
@@ -303,14 +301,14 @@ local function loadNotebook(nb,str,fnIdConvert)
if (not str) then return end
local cnt = nb:GetPageCount()
local sel = nb:GetSelection()
-- store old pages
local currentpages = {}
for i=1,cnt do
local id = nb:GetPageText(i-1)
local newid = fnIdConvert and fnIdConvert(id) or id
currentpages[newid] = {page = nb:GetPage(i-1), text = id, index = i-1}
currentpages[newid] = currentpages[newid] or {}
table.insert(currentpages[newid], {page = nb:GetPage(i-1), text = id, index = i-1})
end
-- remove them
@@ -318,7 +316,7 @@ local function loadNotebook(nb,str,fnIdConvert)
nb:RemovePage(i-1)
end
-- readd them and perform splits
-- read them and perform splits
local direction
local splits = {
X = wx.wxRIGHT,
@@ -337,13 +335,13 @@ local function loadNotebook(nb,str,fnIdConvert)
local instr = cmd:match("<(%w)>")
if (not instr) then
local id = fnIdConvert and fnIdConvert(cmd) or cmd
local page = currentpages[id]
if (page) then
local pageind = next(currentpages[id] or {})
if (pageind) then
local page = currentpages[id][pageind]
currentpages[id][pageind] = nil
nb:AddPage(page.page, page.text)
currentpages[id] = nil
if (direction) then
nb:Split(t, direction)
end
if (direction) then nb:Split(t, direction) end
finishPage(page)
end
end
@@ -351,18 +349,18 @@ local function loadNotebook(nb,str,fnIdConvert)
end
-- add anything we forgot
for i,page in pairs(currentpages) do
nb:AddPage(page.page, page.text)
finishPage(page)
for _,pagelist in pairs(currentpages) do
for _,page in pairs(pagelist) do
nb:AddPage(page.page, page.text)
finishPage(page)
end
end
if (newsel) then
nb:SetSelection(newsel)
end
end
function SettingsRestoreView()
local listname = "/view"
local path = settings:GetPath()
@@ -394,7 +392,7 @@ function SettingsRestoreView()
local layout = settingsReadSafe(settings,"nbbtmlayout",layoutcur)
if (layout ~= layoutcur) then
loadNotebook(ide.frame.bottomnotebook,layout,
function(name) return name:match("console") or name end)
function(name) return name:match("Output") or name end)
end
settings:SetPath(path)
@@ -424,6 +422,7 @@ function SettingsRestoreEditorSettings()
ide.config.interpreter = settingsReadSafe(settings,"interpreter",ide.config.interpreter)
ProjectSetInterpreter(ide.config.interpreter)
end
function SettingsSaveEditorSettings()
local listname = "/editor"
local path = settings:GetPath()

View File

@@ -8,14 +8,11 @@ local ide = ide
local bottomnotebook = ide.frame.bottomnotebook
local out = bottomnotebook.shellbox
local OUTPUT_MARKER = 3
local remotesend
local OUTPUT_MARKER = 3
local OUTPUT_MARKER_VALUE = 8 -- = 2^OUTPUT_MARKER
local frame = ide.frame
out:SetFont(ide.ofont)
out:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, ide.ofont)
out:SetFont(ide.font.oNormal)
out:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, ide.font.oNormal)
out:StyleClearAll()
out:SetBufferedDraw(true)
@@ -26,17 +23,16 @@ out:SetViewWhiteSpace(ide.config.editor.whitespace and true or false)
out:SetIndentationGuides(true)
out:SetWrapMode(wxstc.wxSTC_WRAP_WORD)
out:SetWrapStartIndent(2)
out:SetWrapStartIndent(0)
out:SetWrapVisualFlagsLocation(wxstc.wxSTC_WRAPVISUALFLAGLOC_END_BY_TEXT)
out:SetWrapVisualFlags(wxstc.wxSTC_WRAPVISUALFLAG_START)
out:WrapCount(80)
out:SetWrapVisualFlags(wxstc.wxSTC_WRAPVISUALFLAG_END)
out:MarkerDefine(CURRENT_LINE_MARKER, wxstc.wxSTC_MARK_CHARACTER+string.byte('>'), wx.wxBLACK, wx.wxColour(240, 240, 240))
out:MarkerDefine(BREAKPOINT_MARKER, wxstc.wxSTC_MARK_BACKGROUND, wx.wxBLACK, wx.wxColour(255, 220, 220))
out:MarkerDefine(OUTPUT_MARKER, wxstc.wxSTC_MARK_BACKGROUND, wx.wxBLACK, wx.wxColour(240, 240, 240))
out:SetReadOnly(false)
SetupKeywords(out,"lua",nil,ide.config.stylesoutshell,ide.ofont,ide.ofontItalic)
SetupKeywords(out,"lua",nil,ide.config.stylesoutshell,ide.font.oNormal,ide.font.oItalic)
local function getPromptLine()
local totalLines = out:GetLineCount()
@@ -60,11 +56,12 @@ local function positionInLine(line)
return out:GetCurrentPos() - out:PositionFromLine(line)
end
local function caretOnPromptLine(disallowLeftmost)
local function caretOnPromptLine(disallowLeftmost, line)
local promptLine = getPromptLine()
local currentLine = line or out:GetCurrentLine()
local boundary = disallowLeftmost and 0 or -1
return (out:GetCurrentLine() > promptLine
or out:GetCurrentLine() == promptLine and positionInLine(promptLine) > boundary)
return (currentLine > promptLine
or currentLine == promptLine and positionInLine(promptLine) > boundary)
end
local function chomp(line)
@@ -110,6 +107,8 @@ end
local function shellPrint(marker, ...)
local cnt = select('#',...)
if cnt == 0 then return end -- return if nothing to print
local isPrompt = marker and (getPromptLine() > -1)
local text = ''
@@ -137,6 +136,7 @@ local function shellPrint(marker, ...)
out:EmptyUndoBuffer() -- don't allow the user to undo shell text
out:GotoPos(out:GetLength())
out:EnsureVisibleEnforcePolicy(out:GetLineCount()-1)
end
DisplayShell = function (...)
@@ -155,6 +155,7 @@ end
local function filterTraceError(err, addedret)
local err = err:match("(.-:%d+:.-)\n[^\n]*\n[^\n]*\n[^\n]*src/editor/shellbox.lua:.*in function 'executeShellCode'")
or err
err = err:gsub("stack traceback:.-\n[^\n]+\n?","")
if addedret then err = err:gsub('^%[string "return ', '[string "') end
err = err:match("(.*)\n[^\n]*%(tail call%): %?$") or err
@@ -200,7 +201,7 @@ local function createenv ()
end
local function relativeFilepath(file)
local name,level = luafilepath(3)
local name = luafilepath(3)
return (file and name) and name.."/"..file or file or name
end
@@ -235,53 +236,93 @@ end
local env = createenv()
local function packResults(status, ...) return status, {...} end
local function executeShellCode(tx)
if tx == nil or tx == '' then return end
DisplayShellDirect('\n')
DisplayShellPrompt('')
local addedret = false
local fn,err
if remotesend then
remotesend(tx)
else
fn,err = loadstring(tx)
-- for statement queries create the return
if err and (err:find("'=' expected near '<eof>'") or
err:find("unexpected symbol near '")) then
local errmore
fn,errmore = loadstring("return "..tx:gsub("^%s*=%s*",""))
addedret = not errmore
end
-- try to compile as statement
local _, err = loadstring(tx)
local isstatement = not err
if remotesend then remotesend(tx, isstatement); return end
local addedret, forceexpression = true, tx:match("^%s*=%s*")
tx = tx:gsub("^%s*=%s*","")
fn, err = loadstring("return "..tx)
if not forceexpression and err and
(err:find("'<eof>' expected near '") or
err:find("'%(' expected near") or
err:find("unexpected symbol near '")) then
fn, err = loadstring(tx)
addedret = false
end
if fn == nil and err then
DisplayShellErr(err)
DisplayShellErr(filterTraceError(err, addedret))
elseif fn then
setfenv(fn,env)
local ok, res = xpcall(fn,
-- set the project dir as the current dir to allow "require" calls
-- to work from shell
local projectDir, cwd = FileTreeGetDir()
if projectDir then
cwd = wx.wxFileName.GetCwd()
wx.wxFileName.SetCwd(projectDir)
end
local ok, res = packResults(xpcall(fn,
function(err)
DisplayShellErr(filterTraceError(debug.traceback(err), addedret))
end)
end))
-- restore the current dir
if projectDir then wx.wxFileName.SetCwd(cwd) end
if ok and (addedret or res ~= nil) then DisplayShell(res) end
if ok and (addedret or #res > 0) then
if addedret then
local mobdebug = require "mobdebug"
for i,v in pairs(res) do -- stringify each of the returned values
res[i] = mobdebug.line(v, {nocode = true, comment = 1})
end
-- add nil only if we are forced (using =) or if this is not a statement
-- this is needed to print 'nil' when asked for 'foo',
-- and don't print it when asked for 'print(1)'
if #res == 0 and (forceexpression or not isstatement) then
res = {'nil'}
end
end
DisplayShell((table.unpack or unpack)(res))
end
end
end
function ShellSupportRemote(client,uid)
function ShellSupportRemote(client)
remotesend = client
-- change the name of the tab: console is the second page in the notebook
bottomnotebook:SetPageText(1,
client and "Remote console" or "Local console")
local index = bottomnotebook:GetPageIndex(out)
if index then
bottomnotebook:SetPageText(index,
client and "Remote console" or "Local console")
end
end
function ShellExecuteCode(wfilename)
function ShellExecuteFile(wfilename)
if (not wfilename) then return end
local cmd = 'dofile([['..wfilename:GetFullPath()..']])'
DisplayShellDirect(cmd)
executeShellCode(cmd)
ShellExecuteCode(cmd)
end
function ShellExecuteCode(code)
local index = bottomnotebook:GetPageIndex(bottomnotebook.shellbox)
if ide.config.activateoutput and bottomnotebook:GetSelection() ~= index then
bottomnotebook:SetSelection(index)
end
DisplayShellDirect(code)
executeShellCode(code)
end
local function displayShellIntro()
@@ -336,7 +377,7 @@ out:Connect(wx.wxEVT_KEY_DOWN,
or key == wx.WXK_SHIFT or key == wx.WXK_CONTROL
or key == wx.WXK_ALT then
break
elseif key == wx.WXK_RETURN or key == WXK_NUMPAD_ENTER then
elseif key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER then
if not caretOnPromptLine()
or out:LineFromPosition(out:GetSelectionStart()) < getPromptLine() then
return
@@ -346,10 +387,12 @@ out:Connect(wx.wxEVT_KEY_DOWN,
if caretOnPromptLine(true) and event:ShiftDown() then break end
local promptText = getPromptText()
if #promptText == 0 then return end -- nothing to execute, exit
if promptText == 'clear' then
out:ClearAll()
displayShellIntro()
else
DisplayShellDirect('\n')
executeShellCode(promptText)
end
currentHistory = getPromptLine() -- reset history
@@ -369,4 +412,20 @@ out:Connect(wx.wxEVT_KEY_DOWN,
event:Skip()
end)
local function inputEditable(line)
return caretOnPromptLine(fale, line) and
not (out:LineFromPosition(out:GetSelectionStart()) < getPromptLine())
end
out:Connect(wxstc.wxEVT_STC_UPDATEUI,
function (event) out:SetReadOnly(not inputEditable()) end)
-- only allow copy/move text by dropping to the input line
out:Connect(wxstc.wxEVT_STC_DO_DROP,
function (event)
if not inputEditable(out:LineFromPosition(event:GetPosition())) then
event:SetDragResult(wx.wxDragNone)
end
end)
displayShellIntro()

View File

@@ -11,6 +11,10 @@
-- b bold - boolean
-- i italic - boolean
-- fill fill to end - boolean
-- fn font Face Name - string ("Lucida Console")
-- fx font size - number (11)
-- hs turn hotspot on - true or {r,g,b} 0-255
-- v visibility for symbols of the current style - boolean
function StylesGetDefault()
return {
@@ -148,6 +152,21 @@ function StylesApplyToEditor(styles,editor,font,fontitalic,lexerconvert)
editor:StyleSetUnderline(id, style.u or false)
editor:StyleSetEOLFilled(id, style.fill or false)
if style.fn then editor:StyleSetFaceName(id, style.fn) end
if style.fs then editor:StyleSetSize(id, style.fs) end
if style.v ~= nil then editor:StyleSetVisible(id, style.v) end
if style.hs then
editor:StyleSetHotSpot(id, 1)
-- if passed a color (table) as value, set it as foreground
if type(style.hs) == 'table' then
local color = wx.wxColour(unpack(style.hs))
editor:SetHotspotActiveForeground(1, color)
end
editor:SetHotspotActiveUnderline(1)
editor:SetHotspotSingleLine(1)
end
if (style.fg or defaultfg) then
editor:StyleSetForeground(id, style.fg and wx.wxColour(unpack(style.fg)) or defaultfg)
end
@@ -179,6 +198,10 @@ function StylesApplyToEditor(styles,editor,font,fontitalic,lexerconvert)
for n,outid in pairs(targets) do
applystyle(style,outid)
end
-- allow to specify style numbers, but exclude those styles
-- that may conflict with indicator numbers
elseif (style.st and style.st > 8 and style.st < wxstc.wxSTC_STYLE_DEFAULT) then
applystyle(style,style.st)
end
end
@@ -201,9 +224,9 @@ function ReApplySpecAndStyles()
local errorlog = ide.frame.bottomnotebook.errorlog
local shellbox = ide.frame.bottomnotebook.shellbox
SetupKeywords(shellbox,"lua",nil,ide.config.stylesoutshell,ide.ofont,ide.ofontItalic)
SetupKeywords(shellbox,"lua",nil,ide.config.stylesoutshell,ide.font.oNormal,ide.font.oItalic)
StylesApplyToEditor(ide.config.stylesoutshell,errorlog,ide.ofont,ide.ofontItalic)
StylesApplyToEditor(ide.config.stylesoutshell,errorlog,ide.font.oNormal,ide.font.oItalic)
end
function LoadConfigStyle()
@@ -211,7 +234,7 @@ function LoadConfigStyle()
"/cfg",
"",
"Lua file (*.lua)|*.lua|All files (*)|*",
wx.wxOPEN + wx.wxFILE_MUST_EXIST)
wx.wxFD_OPEN + wx.wxFD_FILE_MUST_EXIST)
if fileDialog:ShowModal() == wx.wxID_OK then
local cfg = {wxstc = wxstc, path = {}, editor = {}, view ={}, acandtip = {}, outputshell = {}, debugger={},}
local cfgfn,err = loadfile(fileDialog:GetPath())
@@ -222,7 +245,7 @@ function LoadConfigStyle()
if not (cfgfn and (cfg.styles or cfg.stylesoutshell)) then
wx.wxMessageBox("Unable to load config style '"..fileDialog:GetPath().."'.",
"wxLua Error",
"Error",
wx.wxOK + wx.wxCENTRE, ide.frame)
else
if (cfg.styles) then

View File

@@ -1,9 +1,15 @@
-- authors: Luxinia Dev (Eike Decker & Christoph Kubisch)
---------------------------------------------------------
package.cpath = package.cpath..';bin/?.dll;bin/clibs/?.dll;bin/clibs/?/?.dll;bin/clibs/?/?/?.dll'
package.cpath = package.cpath..';bin/?.so;bin/clibs/?.so;bin/clibs/?/?.so;bin/clibs/?/?/?.so'
package.path = package.path..'lualibs/?.lua;lualibs/?/?.lua;lualibs/?/init.lua;lualibs/?/?/?.lua;lualibs/?/?/init.lua'
-- put bin/ and lualibs/ first to avoid conflicts with included modules
-- that may have other versions present somewhere else in path/cpath
local iswindows = os.getenv('WINDIR') or (os.getenv('OS') or ''):match('[Ww]indows')
package.cpath = (iswindows
and 'bin/?.dll;bin/clibs/?.dll;'
or 'bin/clibs/?.dylib;bin/lib?.dylib;bin/?.so;bin/clibs/?.so;')
.. package.cpath
package.path = 'lualibs/?.lua;lualibs/?/?.lua;lualibs/?/init.lua;lualibs/?/?/?.lua;lualibs/?/?/init.lua;'
.. package.path
require("wx")
require("bit")
@@ -31,6 +37,7 @@ ide = {
verbose = false,
},
outputshell = {},
filetree = {},
styles = StylesGetDefault(),
stylesoutshell = StylesGetDefault(),
@@ -43,6 +50,9 @@ ide = {
strategy = 2,
},
activateoutput = false, -- activate output/console on Run/Debug/Compile
unhidewxwindow = false, -- try to unhide a wx window
allowinteractivescript = false, -- allow interaction in the output window
filehistorylength = 20,
projecthistorylength = 15,
savebak = false,
@@ -82,10 +92,13 @@ ide = {
-- modTime = wxDateTime of disk file or nil,
-- isModified = bool is the document modified? }
ignoredFilesList = {},
font = nil,
fontItalic = nil,
ofont = nil,
ofontItalic = nil,
font = {
eNormal = nil,
eItalic = nil,
oNormal = nil,
oItalic = nil,
fNormal = nil,
}
}
---------------
@@ -94,11 +107,18 @@ local filenames = {}
local configs = {}
do
local arg = {...}
local fullPath = arg[1] -- first argument must be the application name
assert(type(fullPath) == "string", "first argument must be application name")
if not wx.wxIsAbsolutePath(fullPath) then
fullPath = wx.wxGetCwd().."/"..fullPath
if wx.__WXMSW__ then fullPath = wx.wxUnix2DosFilename(fullPath) end
end
ide.arg = arg
-- first argument must be the application name
assert(type(arg[1]) == "string","first argument must be application name")
ide.editorFilename = arg[1]
ide.config.path.app = arg[1]:match("([%w_-]+)%.?[^%.]*$")
ide.editorFilename = fullPath
ide.osname = wx.wxPlatformInfo.Get():GetOperatingSystemFamilyName()
ide.config.path.app = fullPath:match("([%w_-%.]+)$"):gsub("%.[^%.]*$","")
assert(ide.config.path.app, "no application path defined")
for index = 2, #arg do
if (arg[index] == "-cfg" and index+1 <= #arg) then
@@ -127,7 +147,7 @@ local function addConfig(filename,showerror,isstring)
ide.config.os = os
ide.config.wxstc = wxstc
setfenv(cfgfn,ide.config)
xpcall(function()cfgfn(assert(_G))end,
xpcall(function()cfgfn(assert(_G or _ENV))end,
function(err)
print("Error while executing configuration file: \n",
debug.traceback(err))
@@ -137,11 +157,6 @@ end
do
addConfig(ide.config.path.app.."/config.lua",true)
addConfig("cfg/user.lua",false)
for i,v in ipairs(configs) do
addConfig(v,true,true)
end
configs = nil
end
----------------------
@@ -170,7 +185,7 @@ local function addToTab(tab,file)
local success,result
success, result = xpcall(
function()return cfgfn(_G)end,
function()return cfgfn(_G or _ENV)end,
function(err)
print(("Error while executing configuration file (%s): \n%s"):
format(file,debug.traceback(err)))
@@ -190,8 +205,7 @@ end
-- load interpreters
local function loadInterpreters()
local files = FileSysGet(".\\interpreters\\*.*",wx.wxFILE)
local files = FileSysGet("./interpreters/*.*",wx.wxFILE)
for i,file in ipairs(files) do
if file:match "%.lua$" and app.loadfilters.interpreters(file) then
addToTab(ide.interpreters,file)
@@ -202,8 +216,7 @@ loadInterpreters()
-- load specs
local function loadSpecs()
local files = FileSysGet(".\\spec\\*.*",wx.wxFILE)
local files = FileSysGet("./spec/*.*",wx.wxFILE)
for i,file in ipairs(files) do
if file:match "%.lua$" and app.loadfilters.specs(file) then
addToTab(ide.specs,file)
@@ -214,6 +227,7 @@ local function loadSpecs()
spec.sep = spec.sep or ""
spec.iscomment = {}
spec.iskeyword0 = {}
spec.isstring = {}
if (spec.lexerstyleconvert) then
if (spec.lexerstyleconvert.comment) then
for i,s in pairs(spec.lexerstyleconvert.comment) do
@@ -225,6 +239,11 @@ local function loadSpecs()
spec.iskeyword0[s] = true
end
end
if (spec.lexerstyleconvert.stringtxt) then
for i,s in pairs(spec.lexerstyleconvert.stringtxt) do
spec.isstring[s] = true
end
end
end
end
end
@@ -232,8 +251,7 @@ loadSpecs()
-- load tools
local function loadTools()
local files = FileSysGet(".\\tools\\*.*",wx.wxFILE)
local files = FileSysGet("./tools/*.*",wx.wxFILE)
for i,file in ipairs(files) do
if file:match "%.lua$" and app.loadfilters.tools(file) then
addToTab(ide.tools,file)
@@ -244,6 +262,14 @@ loadTools()
if app.preinit then app.preinit() end
do
addConfig("cfg/user.lua",false)
for i,v in ipairs(configs) do
addConfig(v,true,true)
end
configs = nil
end
---------------
-- Load App
@@ -301,10 +327,16 @@ end
if app.postinit then app.postinit() end
-- only set menu bar *after* postinit handler as it may include adding
-- app-specific menus (Help/About), which are not recognized by MacOS
-- as special items unless SetMenuBar is done after menus are populated.
ide.frame:SetMenuBar(ide.frame.menuBar)
if ide.osname == 'Macintosh' then -- force refresh to fix the filetree
pcall(function() ide.frame:ShowFullScreen(true) ide.frame:ShowFullScreen(false) end)
end
ide.frame:Show(true)
-- Call wx.wxGetApp():MainLoop() last to start the wxWidgets event loop,
-- otherwise the wxLua program will exit immediately.
-- Does nothing if running from wxLua, wxLuaFreeze, or wxLuaEdit since the
-- MainLoop is already running or will be started by the C++ program.
-- call wx.wxGetApp():MainLoop() last to start the wxWidgets event loop,
-- otherwise the program will exit immediately.
-- Does nothing if the MainLoop is already running.
wx.wxGetApp():MainLoop()

View File

@@ -168,8 +168,22 @@ function FileSysGet(dir,spec)
end
local f = browse:FindFirst(dir,spec)
while #f>0 do
table.insert(content,f)
table.insert(content,(f:gsub("^file:",""))) -- remove file: protocol (wx2.9+)
f = browse:FindNext()
end
return content
end
function pairsSorted(t, f)
local a = {}
for n in pairs(t) do table.insert(a, n) end
table.sort(a, f)
local i = 0 -- iterator variable
local iter = function () -- iterator function
i = i + 1
if a[i] == nil then return nil
else return a[i], t[a[i]]
end
end
return iter
end

View File

@@ -15,6 +15,7 @@ return dxpath and {
{ },
{ ID "dx.compile.input", "&Custom Args", "when set a popup for custom compiler args will be envoked", wx.wxITEM_CHECK },
{ ID "dx.compile.legacy", "&Legacy", "when set compiles in legacy mode", wx.wxITEM_CHECK },
{ ID "dx.compile.backwards", "&Backwards Compatibility", "when set compiles in backwards compatibility mode", wx.wxITEM_CHECK },
{ },
{ ID "dx.compile.vertex", "Compile &Vertex", "Compile Vertex shader (select entry word)" },
{ ID "dx.compile.fragment", "Compile &Fragment", "Compile pixel shader (select entry word)" },
@@ -26,7 +27,9 @@ return dxpath and {
local data = {}
data.customarg = false
data.custom = ""
data.legacy = false
data.backwards = false
data.profid = ID ("dx.profile."..dxprofile)
data.domains = {
[ID "dx.compile.vertex"] = 1,
@@ -77,6 +80,10 @@ return dxpath and {
function(event)
data.legacy = event:IsChecked()
end)
frame:Connect(ID "dx.compile.backwards",wx.wxEVT_COMMAND_MENU_SELECTED,
function(event)
data.backwards = event:IsChecked()
end)
-- Compile
local function evCompile(event)
local filename,info = GetEditorFileAndCurInfo()
@@ -92,19 +99,20 @@ return dxpath and {
if (not profile[domain]) then return end
-- popup for custom input
local args = data.customarg and wx.wxGetTextFromUser("Compiler Args") or ""
args = args:len() > 0 and args or nil
data.custom = data.customarg and wx.wxGetTextFromUser("Compiler Args","Dx",data.custom) or data.custom
local args = data.custom:len() > 0 and data.custom or nil
local fullname = filename:GetFullPath()
local outname = fullname.."."..info.selword.."^"
outname = args and outname..args:gsub("%s+%-",";-")..";^" or outname
outname = args and outname..args:gsub("%s*[%-%/]",";-")..";^" or outname
outname = outname..profile[domain]..profile.ext
outname = '"'..outname..'"'
local cmdline = " /T "..profile[domain].." "
cmdline = cmdline..(args and args.." " or "")
cmdline = cmdline..(data.legacy and "/LD " or "")
cmdline = cmdline..(data.backwards and "/Gec " or "")
cmdline = cmdline..data.domaindefs[domain]
cmdline = cmdline.."/Fc "..outname.." "
cmdline = cmdline.."/E "..info.selword.." "

View File

@@ -1,9 +1,12 @@
LICENSE
README
README.md
api/lua/baselib.lua
api/lua/love2d.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
@@ -38,19 +41,48 @@ bin/wxmsw28_qa_vc_custom.dll
bin/wxmsw28_richtext_vc_custom.dll
bin/wxmsw28_stc_vc_custom.dll
bin/wxmsw28_xrc_vc_custom.dll
cfg/user.lua_for_custom_settings.txt
cfg/user-sample.lua
interpreters/love2d.lua
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.lua
lualibs/metalua/mlp_expr.lua
lualibs/metalua/mlp_ext.lua
lualibs/metalua/mlp_lexer.lua
lualibs/metalua/mlp_meta.lua
lualibs/metalua/mlp_misc.lua
lualibs/metalua/mlp_stat.lua
lualibs/metalua/mlp_table.lua
lualibs/metalua/metalua/base.lua
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
@@ -61,7 +93,9 @@ 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
src/editor/menu_edit.lua
src/editor/menu_file.lua
@@ -94,12 +128,15 @@ zbstudio/res/16/wxART_FIND.png
zbstudio/res/16/wxART_FIND_AND_REPLACE.png
zbstudio/res/16/wxART_FOLDER.png
zbstudio/res/16/wxART_GO_DIR_UP.png
zbstudio/res/16/wxART_GO_FORWARD-wxART_OTHER_C.png
zbstudio/res/16/wxART_HELP_PAGE.png
zbstudio/res/16/wxART_LIST_VIEW-wxART_OTHER_C.png
zbstudio/res/16/wxART_NEW_DIR.png
zbstudio/res/16/wxART_NORMAL_FILE-wxART_OTHER_C.png
zbstudio/res/16/wxART_NORMAL_FILE.png
zbstudio/res/16/wxART_PASTE.png
zbstudio/res/16/wxART_REDO.png
zbstudio/res/16/wxART_REPORT_VIEW-wxART_OTHER_C.png
zbstudio/res/16/wxART_UNDO.png
zbstudio/res/32.ico
zbstudio/res/zerobrane.png

View File

@@ -1,6 +1,20 @@
local icons = {}
local CreateBitmap = function(id, client, size)
local width = size:GetWidth()
local key = width .. "/" .. id
local fileClient = "zbstudio/res/" .. key .. "-" .. client .. ".png"
local fileKey = "zbstudio/res/" .. key .. ".png"
local file
if wx.wxFileName(fileClient):FileExists() then file = fileClient
elseif wx.wxFileName(fileKey):FileExists() then file = fileKey
else return wx.wxNullBitmap end
local icon = icons[file] or wx.wxBitmap(file)
icons[file] = icon
return icon
end
local ide = ide
local app = {
createbitmap = CreateBitmap,
loadfilters = {
tools = function(file) return false end,
specs = function(file) return true end,
@@ -9,27 +23,20 @@ local app = {
preinit = function ()
local artProvider = wx.wxLuaArtProvider()
local icons = {}
artProvider.CreateBitmap = function(self, id, client, size)
local width = size:GetWidth()
local key = width .. "/" .. id
local fileClient = "zbstudio/res/" .. key .. "-" .. client .. ".png"
local fileKey = "zbstudio/res/" .. key .. ".png"
local file
if wx.wxFileName(fileClient):FileExists() then file = fileClient
elseif wx.wxFileName(fileKey):FileExists() then file = fileKey
else return wx.wxNullBitmap end
local icon = icons[file] or wx.wxBitmap(file)
icons[file] = icon
return icon
end
artProvider.CreateBitmap = function(self, ...) return CreateBitmap(...) end
wx.wxArtProvider.Push(artProvider)
ide.config.interpreter = "luadeb";
ide.config.interpreter = "luadeb"
ide.config.unhidewxwindow = true -- allow unhiding of wx windows
ide.config.allowinteractivescript = true -- allow interaction in the output window
-- this needs to be in pre-init to load the styles
dofile("src/editor/markup.lua")
end,
postinit = function ()
dofile("zbstudio/menu_help.lua")
dofile("src/editor/inspect.lua")
local bundle = wx.wxIconBundle()
local files = FileSysGet("zbstudio/res/", wx.wxFILE)
@@ -44,9 +51,7 @@ local app = {
local menuBar = ide.frame.menuBar
local menu = menuBar:GetMenu(menuBar:FindMenu("&Project"))
local itemid = menu:FindItem("Lua &interpreter")
if itemid ~= wx.wxNOT_FOUND then menu:Destroy(itemid) end
itemid = menu:FindItem("Project directory")
local itemid = menu:FindItem("Project Directory")
if itemid ~= wx.wxNOT_FOUND then menu:Destroy(itemid) end
menu = menuBar:GetMenu(menuBar:FindMenu("&View"))
@@ -54,6 +59,22 @@ local app = {
if itemid ~= wx.wxNOT_FOUND then menu:Destroy(itemid) end
menuBar:Check(ID_CLEAROUTPUT, true)
-- load myprograms/welcome.lua if exists and no projectdir
local projectdir = ide.config.path.projectdir
if (not projectdir or string.len(projectdir) == 0
or not wx.wxFileName(projectdir):DirExists()) then
local home = wx.wxGetHomeDir():gsub("[\\/]$","")
for _,dir in pairs({home, home.."/Desktop", ""}) do
local fn = wx.wxFileName("myprograms/welcome.lua")
-- normalize to absolute path
if fn:Normalize(wx.wxPATH_NORM_ALL, dir) and fn:FileExists() then
LoadFile(fn:GetFullPath(),nil,true)
ProjectUpdateProjectDir(fn:GetPath(wx.wxPATH_GET_VOLUME))
break
end
end
end
end,
stringtable = {
@@ -64,7 +85,6 @@ local app = {
settingsapp = "ZeroBraneStudio",
settingsvendor = "ZeroBraneLLC",
},
}
return app

View File

@@ -1,4 +1,3 @@
editor.fontname = "Courier New"
editor.caretline = true
editor.showfncall = true
editor.autotabs = false
@@ -6,48 +5,54 @@ editor.usetabs = false
editor.tabwidth = 2
editor.usewrap = true
local G = ... -- this now points to the global environment
if G.ide.osname == 'Macintosh' then filetree.fontsize = 11 end
filehistorylength = 20
singleinstance = true
singleinstanceport = 0xe493
acandtip.shorttip = true
acandtip.shorttip = false
acandtip.nodynwords = true
activateoutput = true
styles = {
-- lexer specific (inherit fg/bg from text)
lexerdef = {fg = {128, 128, 128},},
comment = {fg = {0, 127, 0 },bg = {240, 240, 220}, fill= true,},
stringtxt = {fg = {127, 0, 127},},
stringeol = {fg = {0, 0, 0 },bg = {224, 192, 224}, fill = true, },
preprocessor = {fg = {127, 127, 0 },},
operator = {fg = {0, 0, 0 },},
number = {fg = {90, 100, 0 },},
lexerdef = {fg = {128, 128, 128}},
comment = {fg = {32, 127, 32}, bg = {250, 250, 240}, fill= true},
stringtxt = {fg = {127, 0, 127}},
stringeol = {fg = {0, 0, 0}, bg = {224, 192, 224}, fill = true},
preprocessor = {fg = {127, 127,0}},
operator = {fg = {0, 0, 0}},
number = {fg = {90, 100, 0}},
keywords0 = {fg = {0, 0, 127}, b = true,},
keywords1 = {fg = {127, 0, 0}, b = true,},
keywords2 = {fg = {0, 127, 0}, b = true,},
keywords3 = {fg = {0, 0, 127}, b = true,},
keywords4 = {fg = {127, 0, 95}, b = true,},
keywords5 = {fg = {35, 95, 175}, b = true,},
keywords6 = {fg = {0, 127, 127}, b = true,},
keywords7 = {fg = {240, 255, 255}, b = true,},
keywords0 = {fg = {0, 0, 127}, b = true},
keywords1 = {fg = {127, 0, 0}, b = true},
keywords2 = {fg = {0, 127, 0}, b = true},
keywords3 = {fg = {0, 0, 127}, b = true},
keywords4 = {fg = {127, 0, 95}, b = true},
keywords5 = {fg = {35, 95, 175}, b = true},
keywords6 = {fg = {0, 127, 127}, b = true},
keywords7 = {fg = {240, 255, 255}, b = true},
-- common (inherit fg/bg from text)
text = nil, -- let os pick
linenumber = {fg = {90, 90, 80},},
linenumber = {fg = {90, 90, 80}},
bracematch = {fg = {0, 0, 255}, b = true},
bracemiss = {fg = {255, 0, 0 }, b = true},
ctrlchar = nil,
indent = {fg = {192, 192, 192},bg = {255, 255, 255},},
indent = {fg = {192, 192, 192}, bg = {255, 255, 255}},
calltip = nil,
-- common special (need custom fg & bg)
calltipbg = nil,
sel = nil,
caret = nil,
caretlinebg = nil,
caretlinebg = {bg = {250,250,240}},
fold = nil,
whitespace = nil,
fncall = {fg = {175,175,255}, st= wxstc.wxSTC_INDIC_BOX},
fncall = {fg = {175,175,255}, st= wxstc.wxSTC_INDIC_TT},
}

View File

@@ -6,6 +6,7 @@ local ide = ide
local frame = ide.frame
local menuBar = frame.menuBar
local mobdebug = require "mobdebug"
local helpMenu = wx.wxMenu{
{ ID_ABOUT, "&About\tF1", "About ZeroBrane Studio" },
@@ -54,7 +55,7 @@ local function DisplayAbout(event)
</body>
</html>]]
local dlg = wx.wxDialog(frame, wx.wxID_ANY, "About ZeroBane Studio")
local dlg = wx.wxDialog(frame, wx.wxID_ANY, "About ZeroBrane Studio")
local html = wx.wxLuaHtmlWindow(dlg, wx.wxID_ANY,
wx.wxDefaultPosition, wx.wxSize(440, 270),
wx.wxHW_SCROLLBAR_NEVER)
@@ -72,11 +73,10 @@ local function DisplayAbout(event)
topsizer:Add(html, 1, wx.wxALL, 10)
topsizer:Add(line, 0, wx.wxEXPAND + wx.wxLEFT + wx.wxRIGHT, 10)
topsizer:Add(button, 0, wx.wxALL + wx.wxALIGN_RIGHT, 10)
topsizer:Fit(dlg)
dlg:SetAutoLayout(true)
dlg:SetSizer(topsizer)
topsizer:Fit(dlg)
dlg:ShowModal()
dlg:Destroy()
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B