Repository: akkartik/teliva Branch: main Commit: 618b49fbf3db Files: 169 Total size: 1.6 MB Directory structure: gitextract_eymj815d/ ├── .gitattributes ├── .gitignore ├── COPYRIGHT.md ├── Makefile ├── README.md ├── anagrams.tlv ├── break.tlv ├── chesstv.tlv ├── counter.tlv ├── doc/ │ ├── contents.html │ ├── lua.css │ ├── manual.css │ └── manual.html ├── gemini.tlv ├── graphviz.tlv ├── hanoi.tlv ├── life.tlv ├── lisp.lua ├── lisp.tlv ├── manual_tests ├── sandboxing/ │ ├── README.md │ ├── includes │ ├── system_includes │ └── unique_system_includes ├── shell.nix ├── sieve.tlv ├── smol.tlv ├── src/ │ ├── Makefile │ ├── file.lua │ ├── json.lua │ ├── jsonf.lua │ ├── kilo.c │ ├── lapi.c │ ├── lapi.h │ ├── lauxlib.c │ ├── lauxlib.h │ ├── lbaselib.c │ ├── lcode.c │ ├── lcode.h │ ├── lcurses/ │ │ ├── Makefile │ │ ├── _helpers.c │ │ ├── chstr.c │ │ ├── compat-5.2.c │ │ ├── compat-5.2.h │ │ ├── curses.c │ │ ├── curses.lua │ │ ├── strlcpy.c │ │ └── window.c │ ├── ldebug.c │ ├── ldebug.h │ ├── ldo.c │ ├── ldo.h │ ├── ldump.c │ ├── lfunc.c │ ├── lfunc.h │ ├── lgc.c │ ├── lgc.h │ ├── linit.c │ ├── liolib.c │ ├── llex.c │ ├── llex.h │ ├── llimits.h │ ├── lmathlib.c │ ├── lmem.c │ ├── lmem.h │ ├── lobject.c │ ├── lobject.h │ ├── lopcodes.c │ ├── lopcodes.h │ ├── loslib.c │ ├── lparser.c │ ├── lparser.h │ ├── lstate.c │ ├── lstate.h │ ├── lstring.c │ ├── lstring.h │ ├── lstrlib.c │ ├── ltable.c │ ├── ltable.h │ ├── ltablib.c │ ├── ltm.c │ ├── ltm.h │ ├── lua.c │ ├── lua.h │ ├── luaconf.h │ ├── lualib.h │ ├── luasec/ │ │ ├── Makefile │ │ ├── compat.h │ │ ├── config.c │ │ ├── context.c │ │ ├── context.h │ │ ├── ec.c │ │ ├── ec.h │ │ ├── https.lua │ │ ├── options.c │ │ ├── options.h │ │ ├── options.lua │ │ ├── ssl.c │ │ ├── ssl.h │ │ ├── ssl.lua │ │ ├── x509.c │ │ └── x509.h │ ├── luasocket/ │ │ ├── auxiliar.c │ │ ├── auxiliar.h │ │ ├── buffer.c │ │ ├── buffer.h │ │ ├── compat.c │ │ ├── compat.h │ │ ├── except.c │ │ ├── except.h │ │ ├── ftp.lua │ │ ├── headers.lua │ │ ├── http.lua │ │ ├── inet.c │ │ ├── inet.h │ │ ├── io.c │ │ ├── io.h │ │ ├── ltn12.lua │ │ ├── luasocket.c │ │ ├── luasocket.h │ │ ├── makefile │ │ ├── mbox.lua │ │ ├── mime.c │ │ ├── mime.h │ │ ├── mime.lua │ │ ├── options.c │ │ ├── options.h │ │ ├── pierror.h │ │ ├── select.c │ │ ├── select.h │ │ ├── serial.c │ │ ├── smtp.lua │ │ ├── socket.h │ │ ├── socket.lua │ │ ├── tcp.c │ │ ├── tcp.h │ │ ├── timeout.c │ │ ├── timeout.h │ │ ├── tp.lua │ │ ├── udp.c │ │ ├── udp.h │ │ ├── unix.c │ │ ├── unix.h │ │ ├── unixdgram.c │ │ ├── unixdgram.h │ │ ├── unixstream.c │ │ ├── unixstream.h │ │ ├── url.lua │ │ ├── usocket.c │ │ ├── usocket.h │ │ ├── wsocket.c │ │ └── wsocket.h │ ├── lundump.c │ ├── lundump.h │ ├── lvm.c │ ├── lvm.h │ ├── lzio.c │ ├── lzio.h │ ├── realpath.c │ ├── task.lua │ ├── teliva.c │ ├── teliva.h │ ├── tlv.c │ ├── tlv.h │ └── vimrc.vim ├── template.tlv ├── toot-toot.tlv ├── tour.md └── zet.tlv ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ *.teliva linguist-language=Lua *.tlv linguist-language=Lua ================================================ FILE: .gitignore ================================================ *.o *.a ================================================ FILE: COPYRIGHT.md ================================================ Lua License ----------- Lua is licensed under the terms of the MIT license reproduced below. This means that Lua is free software and can be used for both academic and commercial purposes at absolutely no cost. For details and rationale, see http://www.lua.org/license.html . =============================================================================== Copyright (C) 1994-2012 Lua.org, PUC-Rio. 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. =============================================================================== ================================================ FILE: Makefile ================================================ # makefile for installing Lua # see INSTALL for installation instructions # see src/Makefile and src/luaconf.h for further customization # == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT ======================= # Your platform. See PLATS for possible values. PLAT= none # Where to install. The installation starts in the src and doc directories, # so take care if INSTALL_TOP is not an absolute path. INSTALL_TOP= /usr/local INSTALL_BIN= $(INSTALL_TOP)/bin INSTALL_INC= $(INSTALL_TOP)/include INSTALL_LIB= $(INSTALL_TOP)/lib INSTALL_MAN= $(INSTALL_TOP)/man/man1 # # You probably want to make INSTALL_LMOD and INSTALL_CMOD consistent with # LUA_ROOT, LUA_LDIR, and LUA_CDIR in luaconf.h (and also with etc/lua.pc). INSTALL_LMOD= $(INSTALL_TOP)/share/lua/$V INSTALL_CMOD= $(INSTALL_TOP)/lib/lua/$V # How to install. If your install program does not support "-p", then you # may have to run ranlib on the installed liblua.a (do "make ranlib"). INSTALL= install -p INSTALL_EXEC= $(INSTALL) -m 0755 INSTALL_DATA= $(INSTALL) -m 0644 # # If you don't have install you can use cp instead. # INSTALL= cp -p # INSTALL_EXEC= $(INSTALL) # INSTALL_DATA= $(INSTALL) # Utilities. MKDIR= mkdir -p RANLIB= ranlib # == END OF USER SETTINGS. NO NEED TO CHANGE ANYTHING BELOW THIS LINE ========= # Keep this sync'd with src/Makefile PLATS= freebsd linux macosx netbsd openbsd # What to install. TO_BIN= lua luac TO_INC= lua.h luaconf.h lualib.h lauxlib.h ../etc/lua.hpp TO_LIB= liblua.a TO_MAN= lua.1 luac.1 # Lua version and release. V= 5.1 R= 5.1.5 all: $(PLAT) $(PLATS) clean: cd src && $(MAKE) $@ install: dummy cd src && $(MKDIR) $(INSTALL_BIN) $(INSTALL_INC) $(INSTALL_LIB) $(INSTALL_MAN) $(INSTALL_LMOD) $(INSTALL_CMOD) cd src && $(INSTALL_EXEC) $(TO_BIN) $(INSTALL_BIN) cd src && $(INSTALL_DATA) $(TO_INC) $(INSTALL_INC) cd src && $(INSTALL_DATA) $(TO_LIB) $(INSTALL_LIB) cd doc && $(INSTALL_DATA) $(TO_MAN) $(INSTALL_MAN) ranlib: cd src && cd $(INSTALL_LIB) && $(RANLIB) $(TO_LIB) local: $(MAKE) install INSTALL_TOP=.. none: @echo "Please do" @echo " make PLATFORM" @echo "where PLATFORM is one of these:" @echo " $(PLATS)" @echo "See INSTALL for complete instructions." # make may get confused with test/ and INSTALL in a case-insensitive OS dummy: # echo config parameters echo: @echo "" @echo "These are the parameters currently set in src/Makefile to build Lua $R:" @echo "" @cd src && $(MAKE) -s echo @echo "" @echo "These are the parameters currently set in Makefile to install Lua $R:" @echo "" @echo "PLAT = $(PLAT)" @echo "INSTALL_TOP = $(INSTALL_TOP)" @echo "INSTALL_BIN = $(INSTALL_BIN)" @echo "INSTALL_INC = $(INSTALL_INC)" @echo "INSTALL_LIB = $(INSTALL_LIB)" @echo "INSTALL_MAN = $(INSTALL_MAN)" @echo "INSTALL_LMOD = $(INSTALL_LMOD)" @echo "INSTALL_CMOD = $(INSTALL_CMOD)" @echo "INSTALL_EXEC = $(INSTALL_EXEC)" @echo "INSTALL_DATA = $(INSTALL_DATA)" @echo "" @echo "See also src/luaconf.h ." @echo "" # echo private config parameters pecho: @echo "V = $(V)" @echo "R = $(R)" @echo "TO_BIN = $(TO_BIN)" @echo "TO_INC = $(TO_INC)" @echo "TO_LIB = $(TO_LIB)" @echo "TO_MAN = $(TO_MAN)" # echo config parameters as Lua code # uncomment the last sed expression if you want nil instead of empty strings lecho: @echo "-- installation parameters for Lua $R" @echo "VERSION = '$V'" @echo "RELEASE = '$R'" @$(MAKE) echo | grep = | sed -e 's/= /= "/' -e 's/$$/"/' #-e 's/""/nil/' @echo "-- EOF" # list targets that do not create files (but not all makes understand .PHONY) .PHONY: all $(PLATS) clean test install local none dummy echo pecho lecho # (end of Makefile) ================================================ FILE: README.md ================================================ # Teliva - an environment for end-user programming > “Enable all people to modify the software they use in the course of using it.” > — https://futureofcoding.org/episodes/033.html > “What if we, and all computer users, could reach in and modify our favorite apps?” > — https://www.inkandswitch.com/end-user-programming > “Software must be as easy to change as it is to use.” > — https://malleable.systems ## What's this, then? Teliva is the most naive way to make software as easy to change as it is to use: an interpreted environment for running shareable little text-mode apps. Its language of choice is Lua, excluding all features that can conceivably introduce moving parts in running a Lua program. Here's how you run one of the example apps (the [Tower of Hanoi](https://en.wikipedia.org/wiki/Tower_of_Hanoi)): ```sh git clone https://github.com/akkartik/teliva cd teliva make linux # replace with 'macosx', etc. depending on your OS src/teliva hanoi.tlv ``` ![screenshot of Teliva running the Towers of Hanoi](doc/hanoi.png) No matter what app you run, you are always guaranteed access to a single obvious, consistent way (currently the hotkey `ctrl-u`) to inspect its sources. When you look under the hood of an app, the first thing you see is a _big-picture view_ which shows the app's major data structures and a top-down view of the app's code. ![screenshot of big-picture view for the Towers of Hanoi](doc/hanoi-big-picture.png) Select a definition, make a change, hit `ctrl-x`, and the app will run with your updates. ([video](https://archive.org/details/akkartik-2021-11-14)) You will need some Unix-like platform with a C compiler and the ncurses and openssl libraries. Some possible commands to install them, depending on your OS and package manager of choice: * `guix shell -D lua openssl -- make linux` * `nix-shell --pure` (from a directory containing shell.nix in this repo) * `sudo apt install libncursesw6-dev openssl` * `brew install ncurses openssl` So far I've tested Teliva on Linux, Mac OS X, OpenBSD, NetBSD and FreeBSD; it should work on other flavors of BSD, WSL on Windows, etc. with only minor modifications. ## What else can it do? Anything! Some more sample apps to try out: * Conway's Game of Life, as an example of an animated local app. ``` src/teliva life.tlv ``` [video](https://merveilles.town/@akkartik/107277755421024772) * A viewer for [LiChess TV](https://lichess.org/tv), as an example of animation and accessing a remote API over a network. ``` src/teliva chesstv.tlv ``` [video](https://merveilles.town/@akkartik/107319684018301051) * A browser for the [Gemini protocol](https://gemini.circumlunar.space). ``` src/teliva gemini.tlv ``` [video](https://merveilles.town/@akkartik/107489728557201145) These are just a start. The sky is the limit. ## So, just a programming language, then? There's one big difference with other programming languages: Teliva apps are sandboxed like a browser. Most languages assume the fiction that the person running a program trusts all the code in the program. This assumption hasn't been valid since a decade after the Unix big bang, if then. Teliva takes on the reality that apps can get complex and use code written by strangers. In response, Teliva tries to always summarize for you what the program you're running is trying to do, and to ask you before a random app tries to do something sensitive. Permissions you grant a Teliva app will be _flexible_ and _easy to understand_. Browsers and mobile apps today tend to make you choose between those two properties. The sandboxing experience is still under construction. See [this talk](https://archive.org/details/akkartik-2022-01-16-fosdem) (15 minutes) for a rundown of current thinking. It isn't yet safe to run untrusted Teliva apps you download from the internet. (Fortunately you're unlikely to run into any such apps.) ## Isn't this just an IDE? There's one big difference: these apps are not intended to be runnable outside of the Teliva environment. Editing the sources and visualizing permissions granted will always be core features that are front and center in the UI. A second, more subtle difference: it's primarily an environment for _running_ apps, and only secondarily for editing them. Starting up the environment puts you in a running app by default. Creating an app from a clean slate is a low-priority use case, as is lots of specialized support for developing complex apps. The sweet spot for Teliva is simple apps that people will want to edit after using for a while. ## Who are we trusting by trusting you? Teliva is designed to have a shallow, manageable software supply chain. I rely on packages distributed by the following reputable brands: * A well-known Posix OS, either Linux or BSD. * A standard C library, usually GNU libc. * The [ncurses](https://tldp.org/HOWTO/NCURSES-Programming-HOWTO) library for building text-mode user interfaces. ([Alternative documentation](https://tldp.org/LDP/lpg-0.4.pdf)) Teliva's codebase also includes forks of the following reputable brands: * [Lua 5.1](https://www.lua.org/manual/5.1) * The [Kilo](https://github.com/antirez/kilo) text editor, modified to use ncurses. (Read more about it in this [fantastic walk-through](https://viewsourcecode.org/snaptoken/kilo).) * The [lcurses](https://github.com/lcurses/lcurses) binding for ncurses (as module `curses`). * The [luasocket](https://w3.impa.br/~diego/software/luasocket) library of networking primitives (modules `socket`, `http`, `url`, `headers`, `mime`, `ltn12`). * The [luasec](https://github.com/brunoos/luasec) library for HTTPS support (modules `https` and `ssl`). * The [json.lua](https://github.com/rxi/json.lua) library for serializing/deserializing to JSON (modules `json` and `jsonf`). I only add to this list with great deliberation. Since it includes indirect suppliers (suppliers of suppliers), I have an incentive to only include suppliers who also have shallow supply chains. Minimizing the size of the supply chain should result in more reliable software that requires less frequent upgrades. (Look in [the manual](https://akkartik.github.io/teliva/doc/manual.html) for more details of what's available.) ## Why Lua? It's reputedly the fastest interpreted language per line of implementation code. ## Will it run any Lua program? Not quite. My priority is providing a good experience for newcomers to comprehend and modify the programs they use. If it's not clear how to provide that experience for some kinds of Lua programs, I'd rather disable support for them in Teliva and let people use regular Lua. Or other platforms! - This approach doesn't make sense for batch programs, I think. I also don't yet have a good story for building server programs in this way. - I don't know how to obtain a simple, shallow graphics stack, so there's no support for graphics at the moment. - Teliva initializes the ncurses library by default, so apps should assume they have access to a (color, UTF-8) text-mode window for printing text to, and a keyboard for reading keystrokes from. - Teliva doesn't use files for source code, so the `require` keyword no longer makes sense. You get some libraries preloaded (see below). Beyond those, apps should include all Lua code they want to use. - To create a well-behaved sandbox, Teliva doesn't support adding libraries with C bindings beyond the few it starts up with. - Functions that start with `test_` are special. They're considered automated tests and called without arguments every time an app starts up. - The function `main` is special. It runs every time an app starts up, if all its automated tests pass. - Some functions are disabled because I don't know how to sandbox them effectively: - `os.execute`, `os.getenv`, `io.popen` - `io.lines` (not a security issue; just difficult to distinguish missing files from sandboxing issues) - Some functions are disabled because they don't seem to make sense in an ncurses environment. This includes the Lua notions of default files, which start out as stdin/stdout. - `io.input`, `io.read` - `io.output`, `io.write`, `io.flush` - `curses.getstr()`, `curses.mvgetstr()` (When using these it's easy for the screen to get confusing.) - Some functions in lcurses have [additional smarts](https://github.com/lcurses/lcurses/blob/master/lib/curses.lua). Teliva is [consistent with the underlying ncurses](https://github.com/akkartik/teliva/blob/main/src/lcurses/curses.lua). Look in [the manual](https://akkartik.github.io/teliva/doc/manual.html) for more details of what's available. I may delete more capabilities throughout this stack as I discover features that don't fit well with the Teliva experience. If you find Teliva of use, please [introduce yourself](http://akkartik.name/contact) to me so that I am aware of your use cases. Anybody who is interested can gain a say in its future direction. ## What's with the name? Teliva is the Tamil root for ‘clear’. Very much aspirational. ## Known issues * Colors are currently hardcoded. You get a light background even if your terminal started out dark. To tweak colors, look for calls to `assume_default_colors()` and `init_pair()`, either in .tlv files for a specific app, or in the C sources for the standard code browser/editor. * Backspace is known to not work in some configurations. As a workaround, typing `ctrl-h` tends to work in those situations. * Keys outside the main keyboard area are mostly not supported. This includes the delete key when it's set away from the main keyboard area. (Macs call the backspace key “delete”; it should behave like backspace. As a consequence the menu sometimes mentions keys that don't work, just to encourage people to try options.) * chesstv.tlv silently fails on OpenBSD and NetBSD for some reason I haven't investigated yet. Interestingly, I'm running Teliva with curses on NetBSD but ncurses on OpenBSD, so that is probably not an issue. * life.tlv looks terrible on OpenBSD and NetBSD, with lines wrapping halfway. ## Mirrors and Forks Updates to Teliva can be downloaded from the following mirrors: * https://github.com/akkartik/teliva * https://repo.or.cz/teliva.git * https://codeberg.org/akkartik/teliva * https://tildegit.org/akkartik/teliva * https://git.tilde.institute/akkartik/teliva * https://git.sr.ht/~akkartik/teliva * https://pagure.io/teliva Forks of Teliva are encouraged. If you show me your fork, I'll link to it here. ## Feedback [Most appreciated.](http://akkartik.name/contact) ================================================ FILE: anagrams.tlv ================================================ # .tlv file generated by https://github.com/akkartik/teliva # You may edit it if you are careful; however, you may see cryptic errors if you # violate Teliva's assumptions. # # .tlv files are representations of Teliva programs. Teliva programs consist of # sequences of definitions. Each definition is a table of key/value pairs. Keys # and values are both strings. # # Lines in .tlv files always follow exactly one of the following forms: # - comment lines at the top of the file starting with '#' at column 0 # - beginnings of definitions starting with '- ' at column 0, followed by a # key/value pair # - key/value pairs consisting of ' ' at column 0, containing either a # spaceless value on the same line, or a multi-line value # - multiline values indented by more than 2 spaces, starting with a '>' # # If these constraints are violated, Teliva may unceremoniously crash. Please # report bugs at http://akkartik.name/contact - __teliva_timestamp: original str_helpers: >-- some string helpers from http://lua-users.org/wiki/StringIndexing > >-- index characters using [] >getmetatable('').__index = function(str,i) > if type(i) == 'number' then > return str:sub(i,i) > else > return string[i] > end >end > >-- ranges using (), selected bytes using {} >getmetatable('').__call = function(str,i,j) > if type(i)~='table' then > return str:sub(i,j) > else > local t={} > for k,v in ipairs(i) do > t[k]=str:sub(v,v) > end > return table.concat(t) > end >end > >-- iterate over an ordered sequence >function q(x) > if type(x) == 'string' then > return x:gmatch('.') > else > return ipairs(x) > end >end > >-- insert within string >function string.insert(str1, str2, pos) > return str1:sub(1,pos)..str2..str1:sub(pos+1) >end > >function string.remove(s, pos) > return s:sub(1,pos-1)..s:sub(pos+1) >end > >function string.pos(s, sub) > return string.find(s, sub, 1, true) -- plain=true to disable regular expressions >end > >-- TODO: backport utf-8 support from Lua 5.3 - __teliva_timestamp: original debugy: >debugy = 5 - __teliva_timestamp: original dbg: >-- helper for debug by print; overlay debug information towards the right >-- reset debugy every time you refresh screen >function dbg(window, s) > local oldy = 0 > local oldx = 0 > oldy, oldx = window:getyx() > window:mvaddstr(debugy, 60, s) > debugy = debugy+1 > window:mvaddstr(oldy, oldx, '') >end - __teliva_timestamp: original check: >function check(x, msg) > if x then > Window:addch('.') > else > print('F - '..msg) > print(' '..str(x)..' is false/nil') > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original check_eq: >function check_eq(x, expected, msg) > if eq(x, expected) then > Window:addch('.') > else > print('F - '..msg) > print(' expected '..str(expected)..' but got '..str(x)) > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original eq: >function eq(a, b) > if type(a) ~= type(b) then return false end > if type(a) == 'table' then > if #a ~= #b then return false end > for k, v in pairs(a) do > if b[k] ~= v then > return false > end > end > for k, v in pairs(b) do > if a[k] ~= v then > return false > end > end > return true > end > return a == b >end - __teliva_timestamp: original str: >-- smarter tostring >-- slow; used only for debugging >function str(x) > if type(x) == 'table' then > local result = '' > result = result..#x..'{' > for k, v in pairs(x) do > result = result..str(k)..'='..str(v)..', ' > end > result = result..'}' > return result > elseif type(x) == 'string' then > return '"'..x..'"' > end > return tostring(x) >end - __teliva_timestamp: original find_index: >function find_index(arr, x) > for n, y in ipairs(arr) do > if x == y then > return n > end > end >end - __teliva_timestamp: original trim: >function trim(s) > return s:gsub('^%s*', ''):gsub('%s*$', '') >end - __teliva_timestamp: original split: >function split(s, d) > result = {} > for match in (s..d):gmatch("(.-)"..d) do > table.insert(result, match); > end > return result >end - __teliva_timestamp: original map: >-- only for arrays >function map(l, f) > result = {} > for _, x in ipairs(l) do > table.insert(result, f(x)) > end > return result >end - __teliva_timestamp: original reduce: >-- only for arrays >function reduce(l, f, init) > result = init > for _, x in ipairs(l) do > result = f(result, x) > end > return result >end - __teliva_timestamp: original filter: >function filter(h, f) > result = {} > for k, v in pairs(h) do > if f(k, v) then > result[k] = v > end > end > return result >end - __teliva_timestamp: original ifilter: >-- only for arrays >function ifilter(l, f) > result = {} > for _, x in ipairs(l) do > if f(x) then > table.insert(result, x) > end > end > return result >end - __teliva_timestamp: original sort_letters: >function sort_letters(s) > tmp = {} > for i=1,#s do > table.insert(tmp, s[i]) > end > table.sort(tmp) > local result = '' > for _, c in pairs(tmp) do > result = result..c > end > return result >end > >function test_sort_letters(s) > check_eq(sort_letters(''), '', 'test_sort_letters: empty') > check_eq(sort_letters('ba'), 'ab', 'test_sort_letters: non-empty') > check_eq(sort_letters('abba'), 'aabb', 'test_sort_letters: duplicates') >end - __teliva_timestamp: original count_letters: >-- TODO: handle unicode >function count_letters(s) > local result = {} > for i=1,s:len() do > local c = s[i] > if result[c] == nil then > result[c] = 1 > else > result[c] = result[c] + 1 > end > end > return result >end - __teliva_timestamp: original count: >-- turn an array of elements into a map from elements to their frequency >-- analogous to count_letters for non-strings >function count(a) > local result = {} > for i, v in ipairs(a) do > if result[v] == nil then > result[v] = 1 > else > result[v] = result[v] + 1 > end > end > return result >end - __teliva_timestamp: original union: >function union(a, b) > for k, v in pairs(b) do > a[k] = v > end > return a >end - __teliva_timestamp: original subtract: >-- set subtraction >function subtract(a, b) > for k, v in pairs(b) do > a[k] = nil > end > return a >end - __teliva_timestamp: original all: >-- universal quantifier on sets >function all(s, f) > for k, v in pairs(s) do > if not f(k, v) then > return false > end > end > return true >end - __teliva_timestamp: original to_array: >-- turn a set into an array >-- drops values >function to_array(h) > local result = {} > for k, _ in pairs(h) do > table.insert(result, k) > end > return result >end - __teliva_timestamp: original append: >-- concatenate list 'elems' into 'l', modifying 'l' in the process >function append(l, elems) > for i=1,#elems do > table.insert(l, elems[i]) > end >end - __teliva_timestamp: original prepend: >-- concatenate list 'elems' into the start of 'l', modifying 'l' in the process >function prepend(l, elems) > for i=1,#elems do > table.insert(l, i, elems[i]) > end >end - __teliva_timestamp: original all_but: >function all_but(x, idx) > if type(x) == 'table' then > local result = {} > for i, elem in ipairs(x) do > if i ~= idx then > table.insert(result,elem) > end > end > return result > elseif type(x) == 'string' then > if idx < 1 then return x:sub(1) end > return x:sub(1, idx-1) .. x:sub(idx+1) > else > error('all_but: unsupported type '..type(x)) > end >end > >function test_all_but() > check_eq(all_but('', 0), '', 'all_but: empty') > check_eq(all_but('abc', 0), 'abc', 'all_but: invalid low index') > check_eq(all_but('abc', 4), 'abc', 'all_but: invalid high index') > check_eq(all_but('abc', 1), 'bc', 'all_but: first index') > check_eq(all_but('abc', 3), 'ab', 'all_but: final index') > check_eq(all_but('abc', 2), 'ac', 'all_but: middle index') >end - __teliva_timestamp: original set: >function set(l) > local result = {} > for i, elem in ipairs(l) do > result[elem] = true > end > return result >end - __teliva_timestamp: original set_eq: >function set_eq(l1, l2) > return eq(set(l1), set(l2)) >end > >function test_set_eq() > check(set_eq({1}, {1}), 'set_eq: identical') > check(not set_eq({1, 2}, {1, 3}), 'set_eq: different') > check(set_eq({1, 2}, {2, 1}), 'set_eq: order') > check(set_eq({1, 2, 2}, {2, 1}), 'set_eq: duplicates') >end - __teliva_timestamp: original clear: >function clear(lines) > while #lines > 0 do > table.remove(lines) > end >end - __teliva_timestamp: original zap: >function zap(target, src) > clear(target) > append(target, src) >end - __teliva_timestamp: original mfactorial: >-- memoized version of factorial >-- doesn't memoize recursive calls, but may be good enough >mfactorial = memo1(factorial) - __teliva_timestamp: original factorial: >function factorial(n) > local result = 1 > for i=1,n do > result = result*i > end > return result >end - __teliva_timestamp: original memo1: >-- a higher-order function that takes a function of a single arg >-- (that never returns nil) >-- and returns a memoized version of it >function memo1(f) > local memo = {} > return function(x) > if memo[x] == nil then > memo[x] = f(x) > end > return memo[x] > end >end > >-- mfactorial doesn't seem noticeably faster >function test_memo1() > for i=0,30 do > check_eq(mfactorial(i), factorial(i), 'memo1 over factorial: '..str(i)) > end >end - __teliva_timestamp: original num_permutations: >-- number of permutations of n distinct objects, taken r at a time >function num_permutations(n, r) > return factorial(n)/factorial(n-r) >end > >-- mfactorial doesn't seem noticeably faster >function test_memo1() > for i=0,30 do > for j=0,i do > check_eq(num_permutations(i, j), mfactorial(i)/mfactorial(i-j), 'num_permutations memoizes: '..str(i)..'P'..str(j)) > end > end >end - __teliva_timestamp: original menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = { > {'^h', 'backspace'}, >} - __teliva_timestamp: original Window: >Window = curses.stdscr() - __teliva_timestamp: original doc:blurb: >Show all anagrams of a given word - __teliva_timestamp: original word: >word = '' - __teliva_timestamp: original cursor: >cursor = 1 - __teliva_timestamp: original main: >function main() > Window:nodelay(true) > while true do > render(Window) > update(Window) > end >end - __teliva_timestamp: original update: >function update(window) > local key > while true do > key = window:getch() > if key then break end > end > if key == curses.KEY_LEFT then > if cursor > 1 then > cursor = cursor-1 > end > elseif key == curses.KEY_RIGHT then > if cursor <= #word then > cursor = cursor+1 > end > elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then -- ctrl-h, ctrl-?, delete > if cursor > 1 then > cursor = cursor-1 > word = word:remove(cursor) > end > elseif key >= 32 and key < 127 then > word = word:insert(string.char(key), cursor-1) > cursor = cursor+1 > end >end - __teliva_timestamp: original render: >function render(window) > window:clear() > > local prompt_str = ' what is your name? ' > window:attron(curses.A_REVERSE) > window:mvaddstr(0, 0, prompt_str) > window:attroff(curses.A_REVERSE) > window:addstr(' ') > window:attron(curses.A_BOLD) > window:addstr(word) > window:attroff(curses.A_BOLD) > window:mvaddstr(2, 0, '') > local results = anagrams(word) > if #results > 0 then > window:attron(curses.A_REVERSE) > print(#results..' anagrams') > window:attroff(curses.A_REVERSE) > for i, w in ipairs(results) do > window:addstr(w) > window:addstr(' ') > end > end > > window:mvaddstr(0, string.len(prompt_str)+cursor, '') > window:refresh() >end - __teliva_timestamp: >Mon Feb 21 17:42:28 2022 anagrams: >function anagrams(word) > return gather(sort_letters(word)) >end - __teliva_timestamp: >Mon Feb 21 18:18:07 2022 gather: >function gather(s) > if s == '' then return {} end > local result = {} > for i=1, #s do > if i == 1 or s[i] ~= s[i-1] then > append(result, combine(s[i], gather(all_but(s, i)))) > end > end > return result >end - __teliva_timestamp: original __teliva_note: >basic version combine: >-- return 'l' with each element prefixed with 'prefix' >function combine(prefix, l) > if #l == 0 then return {prefix} end > local result = {} > for _, elem in ipairs(l) do > table.insert(result, prefix..elem) > end > return result >end > >function test_combine() > check_eq(combine('abc', {}), {'abc'}, 'test_combine: empty list') >end - __teliva_timestamp: >Sat Mar 5 15:24:00 2022 count_anagrams: >function count_anagrams(s) > local result = factorial(s:len()) > local letter_counts = count_letters(s) > for l, cnt in pairs(letter_counts) do > result = result / factorial(cnt) > end > return result >end - __teliva_timestamp: >Sat Mar 5 15:53:23 2022 key_pressed: >-- only works when nodelay (non-blocking keyboard) >function key_pressed(window) > local c = window:getch() > if c == nil then return false end > window:ungetch(c) > return true >end - __teliva_timestamp: >Sat Mar 5 15:55:34 2022 render: >function render(window) > window:clear() > > local prompt_str = ' what is your name? ' > window:attron(curses.A_REVERSE) > window:mvaddstr(0, 0, prompt_str) > window:attroff(curses.A_REVERSE) > window:addstr(' ') > window:attron(curses.A_BOLD) > window:addstr(word) > window:attroff(curses.A_BOLD) > window:mvaddstr(2, 0, '') > if #word > 0 then > local num_anagrams = count_anagrams(word) > window:attron(curses.A_REVERSE) > print(num_anagrams..' anagrams') > window:attroff(curses.A_REVERSE) > local results = anagrams(word) > if results == nil then -- interrupted > window:addstr('...') > else > assert(#results == num_anagrams, "something's wrong; the count is unexpected") > for i, w in ipairs(results) do > window:addstr(w) > window:addstr(' ') > if key_pressed(window) then > break > end > end > end > end > > window:mvaddstr(0, string.len(prompt_str)+cursor, '') > window:refresh() >end - __teliva_timestamp: >Sat Mar 5 15:56:35 2022 __teliva_note: >restart computation when a key is pressed gather: >-- return a list of unique permutations of a sorted string 's' >-- the letters in 's' must be in alphabetical order, so that duplicates are adjacent >-- this function can take a long time for long strings, so we make it interruptible >-- if a key is pressed, it returns nil >-- since it's recursive, we also need to handle recursive calls returning nil >function gather(s) > if s == '' then return {} end > local result = {} > for i=1, #s do > if i == 1 or s[i] ~= s[i-1] then > local subresult = gather(all_but(s, i)) > if subresult == nil then return nil end -- interrupted > append(result, combine(s[i], subresult)) > end > if key_pressed(Window) then return nil end -- interrupted > end > return result >end ================================================ FILE: break.tlv ================================================ # .tlv file generated by https://github.com/akkartik/teliva # You may edit it if you are careful; however, you may see cryptic errors if you # violate Teliva's assumptions. # # .tlv files are representations of Teliva programs. Teliva programs consist of # sequences of definitions. Each definition is a table of key/value pairs. Keys # and values are both strings. # # Lines in .tlv files always follow exactly one of the following forms: # - comment lines at the top of the file starting with '#' at column 0 # - beginnings of definitions starting with '- ' at column 0, followed by a # key/value pair # - key/value pairs consisting of ' ' at column 0, containing either a # spaceless value on the same line, or a multi-line value # - multiline values indented by more than 2 spaces, starting with a '>' # # If these constraints are violated, Teliva may unceremoniously crash. Please # report bugs at http://akkartik.name/contact - __teliva_timestamp: original str_helpers: >-- some string helpers from http://lua-users.org/wiki/StringIndexing > >-- index characters using [] >getmetatable('').__index = function(str,i) > if type(i) == 'number' then > return str:sub(i,i) > else > return string[i] > end >end > >-- ranges using (), selected bytes using {} >getmetatable('').__call = function(str,i,j) > if type(i)~='table' then > return str:sub(i,j) > else > local t={} > for k,v in ipairs(i) do > t[k]=str:sub(v,v) > end > return table.concat(t) > end >end > >-- iterate over an ordered sequence >function q(x) > if type(x) == 'string' then > return x:gmatch('.') > else > return ipairs(x) > end >end > >-- insert within string >function string.insert(str1, str2, pos) > return str1:sub(1,pos)..str2..str1:sub(pos+1) >end > >function string.remove(s, pos) > return s:sub(1,pos-1)..s:sub(pos+1) >end > >function string.pos(s, sub) > return string.find(s, sub, 1, true) -- plain=true to disable regular expressions >end > >-- TODO: backport utf-8 support from Lua 5.3 - __teliva_timestamp: original debugy: >debugy = 5 - __teliva_timestamp: original dbg: >-- helper for debug by print; overlay debug information towards the right >-- reset debugy every time you refresh screen >function dbg(window, s) > local oldy = 0 > local oldx = 0 > oldy, oldx = window:getyx() > window:mvaddstr(debugy, 60, s) > debugy = debugy+1 > window:mvaddstr(oldy, oldx, '') >end - __teliva_timestamp: original check: >function check(x, msg) > if x then > Window:addch('.') > else > print('F - '..msg) > print(' '..str(x)..' is false/nil') > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original check_eq: >function check_eq(x, expected, msg) > if eq(x, expected) then > Window:addch('.') > else > print('F - '..msg) > print(' expected '..str(expected)..' but got '..str(x)) > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original eq: >function eq(a, b) > if type(a) ~= type(b) then return false end > if type(a) == 'table' then > if #a ~= #b then return false end > for k, v in pairs(a) do > if b[k] ~= v then > return false > end > end > for k, v in pairs(b) do > if a[k] ~= v then > return false > end > end > return true > end > return a == b >end - __teliva_timestamp: original str: >-- smarter tostring >-- slow; used only for debugging >function str(x) > if type(x) == 'table' then > local result = '' > result = result..#x..'{' > for k, v in pairs(x) do > result = result..str(k)..'='..str(v)..', ' > end > result = result..'}' > return result > elseif type(x) == 'string' then > return '"'..x..'"' > end > return tostring(x) >end - __teliva_timestamp: original find_index: >function find_index(arr, x) > for n, y in ipairs(arr) do > if x == y then > return n > end > end >end - __teliva_timestamp: original trim: >function trim(s) > return s:gsub('^%s*', ''):gsub('%s*$', '') >end - __teliva_timestamp: original split: >function split(s, d) > result = {} > for match in (s..d):gmatch("(.-)"..d) do > table.insert(result, match); > end > return result >end - __teliva_timestamp: original map: >-- only for arrays >function map(l, f) > result = {} > for _, x in ipairs(l) do > table.insert(result, f(x)) > end > return result >end - __teliva_timestamp: original reduce: >-- only for arrays >function reduce(l, f, init) > result = init > for _, x in ipairs(l) do > result = f(result, x) > end > return result >end - __teliva_timestamp: original filter: >function filter(h, f) > result = {} > for k, v in pairs(h) do > if f(k, v) then > result[k] = v > end > end > return result >end - __teliva_timestamp: original ifilter: >-- only for arrays >function ifilter(l, f) > result = {} > for _, x in ipairs(l) do > if f(x) then > table.insert(result, x) > end > end > return result >end - __teliva_timestamp: original sort_letters: >function sort_letters(s) > tmp = {} > for i=1,#s do > table.insert(tmp, s[i]) > end > table.sort(tmp) > local result = '' > for _, c in pairs(tmp) do > result = result..c > end > return result >end > >function test_sort_letters(s) > check_eq(sort_letters(''), '', 'test_sort_letters: empty') > check_eq(sort_letters('ba'), 'ab', 'test_sort_letters: non-empty') > check_eq(sort_letters('abba'), 'aabb', 'test_sort_letters: duplicates') >end - __teliva_timestamp: original count_letters: >-- TODO: handle unicode >function count_letters(s) > local result = {} > for i=1,s:len() do > local c = s[i] > if result[c] == nil then > result[c] = 1 > else > result[c] = result[c] + 1 > end > end > return result >end - __teliva_timestamp: original count: >-- turn an array of elements into a map from elements to their frequency >-- analogous to count_letters for non-strings >function count(a) > local result = {} > for i, v in ipairs(a) do > if result[v] == nil then > result[v] = 1 > else > result[v] = result[v] + 1 > end > end > return result >end - __teliva_timestamp: original union: >function union(a, b) > for k, v in pairs(b) do > a[k] = v > end > return a >end - __teliva_timestamp: original subtract: >-- set subtraction >function subtract(a, b) > for k, v in pairs(b) do > a[k] = nil > end > return a >end - __teliva_timestamp: original all: >-- universal quantifier on sets >function all(s, f) > for k, v in pairs(s) do > if not f(k, v) then > return false > end > end > return true >end - __teliva_timestamp: original to_array: >-- turn a set into an array >-- drops values >function to_array(h) > local result = {} > for k, _ in pairs(h) do > table.insert(result, k) > end > return result >end - __teliva_timestamp: original append: >-- concatenate list 'elems' into 'l', modifying 'l' in the process >function append(l, elems) > for i=1,#elems do > table.insert(l, elems[i]) > end >end - __teliva_timestamp: original prepend: >-- concatenate list 'elems' into the start of 'l', modifying 'l' in the process >function prepend(l, elems) > for i=1,#elems do > table.insert(l, i, elems[i]) > end >end - __teliva_timestamp: original all_but: >function all_but(x, idx) > if type(x) == 'table' then > local result = {} > for i, elem in ipairs(x) do > if i ~= idx then > table.insert(result,elem) > end > end > return result > elseif type(x) == 'string' then > if idx < 1 then return x:sub(1) end > return x:sub(1, idx-1) .. x:sub(idx+1) > else > error('all_but: unsupported type '..type(x)) > end >end > >function test_all_but() > check_eq(all_but('', 0), '', 'all_but: empty') > check_eq(all_but('abc', 0), 'abc', 'all_but: invalid low index') > check_eq(all_but('abc', 4), 'abc', 'all_but: invalid high index') > check_eq(all_but('abc', 1), 'bc', 'all_but: first index') > check_eq(all_but('abc', 3), 'ab', 'all_but: final index') > check_eq(all_but('abc', 2), 'ac', 'all_but: middle index') >end - __teliva_timestamp: original set: >function set(l) > local result = {} > for i, elem in ipairs(l) do > result[elem] = true > end > return result >end - __teliva_timestamp: original set_eq: >function set_eq(l1, l2) > return eq(set(l1), set(l2)) >end > >function test_set_eq() > check(set_eq({1}, {1}), 'set_eq: identical') > check(not set_eq({1, 2}, {1, 3}), 'set_eq: different') > check(set_eq({1, 2}, {2, 1}), 'set_eq: order') > check(set_eq({1, 2, 2}, {2, 1}), 'set_eq: duplicates') >end - __teliva_timestamp: original clear: >function clear(lines) > while #lines > 0 do > table.remove(lines) > end >end - __teliva_timestamp: original zap: >function zap(target, src) > clear(target) > append(target, src) >end - __teliva_timestamp: original mfactorial: >-- memoized version of factorial >-- doesn't memoize recursive calls, but may be good enough >mfactorial = memo1(factorial) - __teliva_timestamp: original factorial: >function factorial(n) > local result = 1 > for i=1,n do > result = result*i > end > return result >end - __teliva_timestamp: original memo1: >-- a higher-order function that takes a function of a single arg >-- (that never returns nil) >-- and returns a memoized version of it >function memo1(f) > local memo = {} > return function(x) > if memo[x] == nil then > memo[x] = f(x) > end > return memo[x] > end >end > >-- mfactorial doesn't seem noticeably faster >function test_memo1() > for i=0,30 do > check_eq(mfactorial(i), factorial(i), 'memo1 over factorial: '..str(i)) > end >end - __teliva_timestamp: original num_permutations: >-- number of permutations of n distinct objects, taken r at a time >function num_permutations(n, r) > return factorial(n)/factorial(n-r) >end > >-- mfactorial doesn't seem noticeably faster >function test_memo1() > for i=0,30 do > for j=0,i do > check_eq(num_permutations(i, j), mfactorial(i)/mfactorial(i-j), 'num_permutations memoizes: '..str(i)..'P'..str(j)) > end > end >end - __teliva_timestamp: original menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = {} - __teliva_timestamp: original Window: >Window = curses.stdscr() - __teliva_timestamp: original window: >-- constructor for fake screen and window >-- call it like this: >-- local w = window{ >-- kbd=kbd('abc'), >-- scr=scr{h=5, w=4}, >-- } >-- eventually it'll do everything a real ncurses window can >function window(h) > h.__index = h > setmetatable(h, h) > h.__index = function(table, key) > return rawget(h, key) > end > h.attrset = function(self, x) > self.scr.attrs = x > end > h.attron = function(self, x) > -- currently same as attrset since Lua 5.1 doesn't have bitwise operators > -- doesn't support multiple attrs at once >-- local old = self.scr.attrs >-- self.scr.attrs = old|x > self.scr.attrs = x > end > h.attroff = function(self, x) > -- currently borked since Lua 5.1 doesn't have bitwise operators > -- doesn't support multiple attrs at once >-- local old = self.scr.attrs >-- self.scr.attrs = old & (~x) > self.scr.attrs = curses.A_NORMAL > end > h.getch = function(self) > local c = table.remove(h.kbd, 1) > if c == nil then return c end > return string.byte(c) -- for verisimilitude with ncurses > end > h.addch = function(self, c) > local scr = self.scr > if c == '\n' then > scr.cursy = scr.cursy+1 > scr.cursx = 0 > return > end > if scr.cursy <= scr.h then > scr[scr.cursy][scr.cursx] = {data=c, attrs=scr.attrs} > scr.cursx = scr.cursx+1 > if scr.cursx > scr.w then > scr.cursy = scr.cursy+1 > scr.cursx = 1 > end > end > end > h.addstr = function(self, s) > for i=1,s:len() do > self:addch(s[i]) > end > end > h.mvaddch = function(self, y, x, c) > self.scr.cursy = y > self.scr.cursx = x > self:addch(c) > end > h.mvaddstr = function(self, y, x, s) > self.scr.cursy = y > self.scr.cursx = x > self:addstr(s) > end > h.clear = function(self) > clear_scr(self.scr) > end > h.refresh = function(self) > -- nothing > end > return h >end - __teliva_timestamp: original kbd: >function kbd(keys) > local result = {} > for i=1,keys:len() do > table.insert(result, keys[i]) > end > return result >end - __teliva_timestamp: original scr: >function scr(props) > props.cursx = 1 > props.cursy = 1 > clear_scr(props) > return props >end - __teliva_timestamp: original clear_scr: >function clear_scr(props) > props.cursy = 1 > props.cursx = 1 > for y=1,props.h do > props[y] = {} > for x=1,props.w do > props[y][x] = {data=' ', attrs=curses.A_NORMAL} > end > end > return props >end - __teliva_timestamp: original check_screen: >function check_screen(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > check_eq(window.scr[y][x].data, contents[i], message..'/'..y..','..x) > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end > >-- putting it all together, an example test of both keyboard and screen >function test_check_screen() > local lines = { > c='123', > d='234', > a='345', > b='456', > } > local w = window{ > kbd=kbd('abc'), > scr=scr{h=3, w=5}, > } > local y = 1 > while true do > local b = w:getch() > if b == nil then break end > w:mvaddstr(y, 1, lines[string.char(b)]) > y = y+1 > end > check_screen(w, '345 '.. > '456 '.. > '123 ', > 'test_check_screen') >end - __teliva_timestamp: original check_reverse: >function check_reverse(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > if contents[i] ~= ' ' then > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & curses.A_REVERSE, message..'/'..y..','..x) > check_eq(window.scr[y][x].attrs, curses.A_REVERSE, message..'/'..y..','..x) > else > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & (~curses.A_REVERSE), message..'/'..y..','..x) > check(window.scr[y][x].attrs ~= curses.A_REVERSE, message..'/'..y..','..x) > end > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end - __teliva_timestamp: original check_bold: >function check_bold(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > if contents[i] ~= ' ' then > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & curses.A_BOLD, message..'/'..y..','..x) > check_eq(window.scr[y][x].attrs, curses.A_BOLD, message..'/'..y..','..x) > else > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & (~curses.A_BOLD), message..'/'..y..','..x) > check(window.scr[y][x].attrs ~= curses.A_BOLD, message..'/'..y..','..x) > end > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end - __teliva_timestamp: original check_color: >-- check which parts of a screen have the given color_pair >function check_color(window, cp, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > if contents[i] ~= ' ' then > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & curses.color_pair(cp), message..'/'..y..','..x) > check_eq(window.scr[y][x].attrs, curses.color_pair(cp), message..'/'..y..','..x) > else > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & (~curses.A_BOLD), message..'/'..y..','..x) > check(window.scr[y][x].attrs ~= curses.color_pair(cp), message..'/'..y..','..x) > end > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end - __teliva_timestamp: original sep: >-- horizontal separator >function sep(window) > local y, _ = window:getyx() > window:mvaddstr(y+1, 0, '') > local _, cols = window:getmaxyx() > for col=1,cols do > window:addstr('_') > end >end - __teliva_timestamp: original render: >function render(window) > window:clear() > -- draw stuff to screen here > window:attron(curses.A_BOLD) > window:mvaddstr(1, 5, "example app") > window:attrset(curses.A_NORMAL) > for i=0,15 do > window:attrset(curses.color_pair(i)) > window:mvaddstr(3+i, 5, "========================") > end > window:refresh() >end - __teliva_timestamp: original update: >function update(window) > local key = window:getch() > -- process key here >end - __teliva_timestamp: original init_colors: >function init_colors() > for i=0,7 do > curses.init_pair(i, i, -1) > end > curses.init_pair(8, 7, 0) > curses.init_pair(9, 7, 1) > curses.init_pair(10, 7, 2) > curses.init_pair(11, 7, 3) > curses.init_pair(12, 7, 4) > curses.init_pair(13, 7, 5) > curses.init_pair(14, 7, 6) > curses.init_pair(15, -1, 15) >end - __teliva_timestamp: original main: >function main() > init_colors() > > while true do > render(Window) > update(Window) > end >end - __teliva_timestamp: >Thu Mar 17 21:21:29 2022 program: >program = {lines = nil, cursor = nil} >-- lines: [line] >-- cursor: int, index into lines >-- line: {[word], cursor} >-- word: {string, cursor} - __teliva_timestamp: >Thu Mar 17 21:27:38 2022 render: >function render(window) > local key = window:getch() > for row, line in ipair(program.lines) do > for col, word in ipair(line.words) do > window:addstr(word) > window:addstr(' ') > end > window:addstr('\n') > end >end - __teliva_timestamp: >Thu Mar 17 21:28:39 2022 render: >function render(window) > local key = window:getch() > for row, line in ipairs(program.lines) do > for col, word in pairs(line.words) do > window:addstr(word) > window:addstr(' ') > end > window:addstr('\n') > end >end - __teliva_timestamp: >Thu Mar 17 21:31:33 2022 Program: >Program = { > cursor_y = 1, > lines = { > { > cursor_x = 1, > words = { > { > cursor = 0, > data = '', > }, > }, > }, > }, >} > > > > > - __teliva_timestamp: >Thu Mar 17 21:32:05 2022 render: >function render(window) > window:clear() > for row, line in ipairs(program.lines) do > for col, word in pairs(line.words) do > window:addstr(word) > window:addstr(' ') > end > window:addstr('\n') > end > window:refresh() >end - __teliva_timestamp: >Thu Mar 17 21:32:22 2022 render: >function render(window) > window:clear() > for row, line in ipairs(program.lines) do > for col, word in pairs(line.words) do > window:addstr(word.data) > window:addstr(' ') > end > window:addstr('\n') > end > window:refresh() >end - __teliva_timestamp: >Thu Mar 17 21:33:00 2022 update: >function update(window) > local key = window:getch() > if key == ' ' then > print('space') > window:getch() > end >end - __teliva_timestamp: >Thu Mar 17 21:33:17 2022 update: >function update(window) > local key = window:getch() > if key == string.byte(' ') then > print('space') > window:getch() > end >end - __teliva_timestamp: >Thu Mar 17 21:33:31 2022 update: >function update(window) > local key = string.char(window:getch()) > if key == ' ' then > print('space') > window:getch() > end >end - __teliva_timestamp: >Thu Mar 17 21:36:14 2022 __teliva_note: >very initial skeleton >- just render, no eval >- no stack rendering >- no cursor (just appends) update: >function update(window) > local key = string.char(window:getch()) > if key == ' ' then > table.insert(program.lines[1].words, {data='', cursor=0}) > else > local words = program.lines[1].words > words[#words].data = words[#words].data .. key > end >end - __teliva_timestamp: >Thu Mar 17 21:39:24 2022 main: >function main() > init_colors() > > while true do > render(Window) > update(Window) > end >end > >function test_basic_render() > - __teliva_timestamp: >Thu Mar 17 21:43:05 2022 main: >function main() > init_colors() > > while true do > render(Window, Program) > update(Window, Program) > end >end - __teliva_timestamp: >Thu Mar 17 21:43:11 2022 render: >function render(window, program) > window:clear() > for row, line in ipairs(program.lines) do > for col, word in pairs(line.words) do > window:addstr(word.data) > window:addstr(' ') > end > window:addstr('\n') > end > window:refresh() >end > >function test_render() > local w = window{ > kbd=kbd(''), > scr=scr{h=3, w=5}, > } >end - __teliva_timestamp: >Thu Mar 17 21:43:20 2022 update: >function update(window, program, key) > if key == ' ' then > table.insert(program.lines[1].words, {data='', cursor=0}) > else > local words = program.lines[1].words > words[#words].data = words[#words].data .. key > end >end - __teliva_timestamp: >Thu Mar 17 21:58:10 2022 Program: >Program = empty_program() - __teliva_timestamp: >Thu Mar 17 21:58:10 2022 empty_program: >function empty_program() > return { > cursor_y = 1, > lines = { > { > cursor_x = 1, > words = { > { > cursor = 0, > data = '', > }, > }, > }, > }, > } >end - __teliva_timestamp: >Thu Mar 17 22:36:24 2022 render: >function render(window, program) > window:clear() > for row, line in ipairs(program.lines) do > for col, word in pairs(line.words) do > window:addstr(word.data) > window:addstr(' ') > end > window:addstr('\n') > end > window:refresh() >end - __teliva_timestamp: >Thu Mar 17 22:41:01 2022 __teliva_note: >a passing test main: >function main() > init_colors() > > while true do > render(Window, Program) > local key = string.char(Window:getch()) -- nodelay can't be set > update(Window, Program, key) > end >end > >function test_incomplete() > local w = window{ > kbd=kbd('ab c'), > scr=scr{h=3, w=5}, > } > local prog = empty_program() > while true do > render(w, prog) > local key = w:getch() > if key == nil then break end > update(w, prog, string.char(key)) > end > check_screen(w, 'ab c '.. > ' '.. > ' ', > 'test_main_incomplete') >end - __teliva_timestamp: original doc:blurb: >A concatenative programming language with a live-updating debug UI. >by Kartik Agaram and Sumeet Agarwal > >Highly incomplete. > >Previous prototypes: > - https://archive.org/details/akkartik-2min-2020-12-06 > - https://merveilles.town/@akkartik/105759816342173743 ================================================ FILE: chesstv.tlv ================================================ # .tlv file generated by https://github.com/akkartik/teliva # You may edit it if you are careful; however, you may see cryptic errors if you # violate Teliva's assumptions. # # .tlv files are representations of Teliva programs. Teliva programs consist of # sequences of definitions. Each definition is a table of key/value pairs. Keys # and values are both strings. # # Lines in .tlv files always follow exactly one of the following forms: # - comment lines at the top of the file starting with '#' at column 0 # - beginnings of definitions starting with '- ' at column 0, followed by a # key/value pair # - key/value pairs consisting of ' ' at column 0, containing either a # spaceless value on the same line, or a multi-line value # - multiline values indented by more than 2 spaces, starting with a '>' # # If these constraints are violated, Teliva may unceremoniously crash. Please # report bugs at http://akkartik.name/contact - __teliva_timestamp: original check: >function check(x, msg) > if x then > Window:addch('.') > else > print('F - '..msg) > print(' '..str(x)..' is false/nil') > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original check_eq: >function check_eq(x, expected, msg) > if eq(x, expected) then > Window:addch('.') > else > print('F - '..msg) > print(' expected '..str(expected)..' but got '..str(x)) > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original eq: >function eq(a, b) > if type(a) ~= type(b) then return false end > if type(a) == 'table' then > if #a ~= #b then return false end > for k, v in pairs(a) do > if b[k] ~= v then > return false > end > end > for k, v in pairs(b) do > if a[k] ~= v then > return false > end > end > return true > end > return a == b >end - __teliva_timestamp: original str: >-- smarter tostring >-- slow; used only for debugging >function str(x) > if type(x) == 'table' then > local result = '' > result = result..#x..'{' > for k, v in pairs(x) do > result = result..str(k)..'='..str(v)..', ' > end > result = result..'}' > return result > elseif type(x) == 'string' then > return '"'..x..'"' > end > return tostring(x) >end - __teliva_timestamp: original Window: >Window = curses.stdscr() >-- animation-based app >Window:nodelay(true) >lines, cols = Window:getmaxyx() - __teliva_timestamp: original current_game: >current_game = {} - __teliva_timestamp: original piece_glyph: >piece_glyph = { > -- for legibility, white pieces also use unicode glyphs for black pieces > -- we rely on colors to distinguish them > K = 0x265a, > Q = 0x265b, > R = 0x265c, > B = 0x265d, > N = 0x265e, > P = 0x265f, > k = 0x265a, > q = 0x265b, > r = 0x265c, > b = 0x265d, > n = 0x265e, > p = 0x265f, >} - __teliva_timestamp: original top_player: >function top_player(current_game) > if current_game.players[1].color == "black" then > return current_game.players[1] > end > return current_game.players[2] >end - __teliva_timestamp: original bottom_player: >function bottom_player(current_game) > if current_game.players[1].color == "white" then > return current_game.players[1] > end > return current_game.players[2] >end - __teliva_timestamp: original render_player: >function render_player(y, x, player) > Window:mvaddstr(y, x, player.user.name) > Window:addstr(" · ") > Window:addstr(tostring(player.rating)) >end - __teliva_timestamp: original render_square: >function render_square(current_game, rank, file, highlighted_squares) > -- decide whether to highlight > local hl = 0 > if (rank == highlighted_squares.from.rank and file == highlighted_squares.from.file) > or (rank == highlighted_squares.to.rank and file == highlighted_squares.to.file) then > hl = 4 > end > if (rank+file)%2 == 1 then > -- light square > Window:attrset(curses.color_pair(1+hl)) > else > -- dark square > Window:attrset(curses.color_pair(3+hl)) > end > Window:mvaddstr((8 - rank + 1)*3, file*5, " ") > Window:mvaddstr((8 - rank + 1)*3+1, file*5, " ") > Window:mvaddstr((8 - rank + 1)*3+2, file*5, " ") > Window:attrset(curses.A_NORMAL) >end - __teliva_timestamp: original render_fen_rank: >function render_fen_rank(rank, fen_rank, highlighted_squares) > local file = 1 > for x in fen_rank:gmatch(".") do > if x:match("%d") then > file = file + tonumber(x) > else > -- decide whether to highlight > local hl = 0 > if (rank == highlighted_squares.from.rank and file == highlighted_squares.from.file) > or (rank == highlighted_squares.to.rank and file == highlighted_squares.to.file) then > hl = 4 > end > if (rank+file)%2 == 1 then > if x < 'Z' then > -- white piece on light square > Window:attrset(curses.color_pair(1+hl)) > else > -- black piece on light square > Window:attrset(curses.color_pair(2+hl)) > end > else > if x < 'Z' then > -- white piece on dark square > Window:attrset(curses.color_pair(3+hl)) > else > -- black piece on dark square > Window:attrset(curses.color_pair(4+hl)) > end > end > Window:mvaddstr((8 - rank + 1)*3+1, file*5+2, utf8(piece_glyph[x])) > Window:attrset(curses.A_NORMAL) > file = file + 1 > end > end >end - __teliva_timestamp: original render_time: >function render_time(y, x, seconds) > if seconds == nil then return end > Window:mvaddstr(y, x, tostring(math.floor(seconds/60))) > Window:addstr(string.format(":%02d", seconds%60)) >end - __teliva_timestamp: original render_board: >function render_board(current_game) >--? Window:mvaddstr(1, 50, dump(current_game.fen)) >--? Window:mvaddstr(6, 50, dump(current_game.previously_moved_squares)) > render_player(2, 5, top_player(current_game)) > render_time(2, 35, current_game.bc) > for rank=8,1,-1 do > for file=1,8 do > render_square(current_game, rank, file, current_game.previously_moved_squares) > end > render_fen_rank(rank, current_game.fen_rank[8-rank+1], current_game.previously_moved_squares) > end > render_player(27, 5, bottom_player(current_game)) > render_time(27, 35, current_game.wc) >end - __teliva_timestamp: original parse_lm: >function parse_lm(move) >--? Window:mvaddstr(4, 50, move) > local file1 = string.byte(move:sub(1, 1)) - 96 -- 'a'-1 > local rank1 = string.byte(move:sub(2, 2)) - 48 -- '0' > local file2 = string.byte(move:sub(3, 3)) - 96 -- 'a'-1 > local rank2 = string.byte(move:sub(4, 4)) - 48 -- '0' >--? Window:mvaddstr(5, 50, dump({{rank1, file1}, {rank2, file2}})) > return {from={rank=rank1, file=file1}, to={rank=rank2, file=file2}} >end - __teliva_timestamp: original render: >function render(chunk) > local o = json.decode(chunk) > if o.t == "featured" then > current_game = o.d >--? current_game.lm = "__" > current_game.previously_moved_squares = {from={rank=0, file=0}, to={rank=0, file=0}} -- no highlight > else > current_game.fen = o.d.fen > current_game.wc = o.d.wc > current_game.bc = o.d.bc >--? current_game.lm = o.d.lm > current_game.previously_moved_squares = parse_lm(o.d.lm) >--? Window:nodelay(false) >--? Window:mvaddstr(3, 50, "paused") > end > current_game.fen_rank = split(current_game.fen, "%w+") > render_board(current_game) > Window:refresh() >end - __teliva_timestamp: original init_colors: >function init_colors() > -- colors > local light_piece = 1 > local dark_piece = 0 > local light_square = 252 > local dark_square = 242 > local light_last_moved_square = 229 > local dark_last_moved_square = 226 > -- initialize colors > curses.init_pair(1, light_piece, light_square) > curses.init_pair(2, dark_piece, light_square) > curses.init_pair(3, light_piece, dark_square) > curses.init_pair(4, dark_piece, dark_square) > curses.init_pair(5, light_piece, light_last_moved_square) > curses.init_pair(6, dark_piece, light_last_moved_square) > curses.init_pair(7, light_piece, dark_last_moved_square) > curses.init_pair(8, dark_piece, dark_last_moved_square) >end - __teliva_timestamp: original main: >function main() > init_colors() > local request = { > url = "https://lichess.org/api/tv/feed", > sink = function(chunk, err) > if chunk then > Window:clear() > render(chunk) > Window:getch() > end > return 1 > end, > } > http.request(request) >end - __teliva_timestamp: original utf8: >-- https://stackoverflow.com/questions/7983574/how-to-write-a-unicode-symbol-in-lua >function utf8(decimal) > local bytemarkers = { {0x7FF,192}, {0xFFFF,224}, {0x1FFFFF,240} } > if decimal<128 then return string.char(decimal) end > local charbytes = {} > for bytes,vals in ipairs(bytemarkers) do > if decimal<=vals[1] then > for b=bytes+1,2,-1 do > local mod = decimal%64 > decimal = (decimal-mod)/64 > charbytes[b] = string.char(128+mod) > end > charbytes[1] = string.char(vals[2]+decimal) > break > end > end > return table.concat(charbytes) >end - __teliva_timestamp: original split: >function split(s, pat) > result = {} > for x in s:gmatch(pat) do > table.insert(result, x) > end > return result >end - __teliva_timestamp: original dump: >-- https://stackoverflow.com/questions/9168058/how-to-dump-a-table-to-console >function dump(o) > if type(o) == 'table' then > local s = '{ ' > for k,v in pairs(o) do > if type(k) ~= 'number' then k = '"'..k..'"' end > s = s .. '['..k..'] = ' .. dump(v) .. ',' > end > return s .. '} ' > else > return tostring(o) > end >end - __teliva_timestamp: >Sat Feb 12 18:57:02 2022 __teliva_note: >better UX when app isn't permitted to access the network main: >function main() > init_colors() > local request = { > url = "https://lichess.org/api/tv/feed", > sink = function(chunk, err) > if chunk then > Window:clear() > -- main event loop is here > render(chunk) > Window:getch() > end > return 1 > end, > } > http.request(request) > -- degenerate event loop just to show errors in sandboxing, etc. > while true do Window:getch(); end >end - __teliva_timestamp: >Thu Feb 17 20:00:23 2022 doc:blurb: >A demo of Teliva's support for networking: watch the current live chess game from Lichess TV. > >Compare with https://lichess.org/tv on a browser. ================================================ FILE: counter.tlv ================================================ # .tlv file generated by https://github.com/akkartik/teliva # You may edit it if you are careful; however, you may see cryptic errors if you # violate Teliva's assumptions. # # .tlv files are representations of Teliva programs. Teliva programs consist of # sequences of definitions. Each definition is a table of key/value pairs. Keys # and values are both strings. # # Lines in .tlv files always follow exactly one of the following forms: # - comment lines at the top of the file starting with '#' at column 0 # - beginnings of definitions starting with '- ' at column 0, followed by a # key/value pair # - key/value pairs consisting of ' ' at column 0, containing either a # spaceless value on the same line, or a multi-line value # - multiline values indented by more than 2 spaces, starting with a '>' # # If these constraints are violated, Teliva may unceremoniously crash. Please # report bugs at http://akkartik.name/contact - __teliva_timestamp: original check: >function check(x, msg) > if x then > Window:addch('.') > else > print('F - '..msg) > print(' '..str(x)..' is false/nil') > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original check_eq: >function check_eq(x, expected, msg) > if eq(x, expected) then > Window:addch('.') > else > print('F - '..msg) > print(' expected '..str(expected)..' but got '..str(x)) > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original eq: >function eq(a, b) > if type(a) ~= type(b) then return false end > if type(a) == 'table' then > if #a ~= #b then return false end > for k, v in pairs(a) do > if b[k] ~= v then > return false > end > end > for k, v in pairs(b) do > if a[k] ~= v then > return false > end > end > return true > end > return a == b >end - __teliva_timestamp: original str: >-- smarter tostring >-- slow; used only for debugging >function str(x) > if type(x) == 'table' then > local result = '' > result = result..#x..'{' > for k, v in pairs(x) do > result = result..str(k)..'='..str(v)..', ' > end > result = result..'}' > return result > elseif type(x) == 'string' then > return '"'..x..'"' > end > return tostring(x) >end - __teliva_timestamp: original Window: >Window = curses.stdscr() - __teliva_timestamp: original n: >n = 0 - __teliva_timestamp: original render: >function render(window) > window:clear() > window:attron(curses.A_BOLD) > window:attron(curses.color_pair(6)) > window:mvaddstr(10, 10, " ") > window:mvaddstr(10, 11, n) > window:attroff(curses.color_pair(6)) > window:attroff(curses.A_BOLD) > window:refresh() >end - __teliva_timestamp: original menu: >menu = { > {"Enter", "increment"} >} - __teliva_timestamp: original update: >function update(window) > local key = window:getch() > if key == 10 then > n = n+1 > end >end - __teliva_timestamp: original main: >function main() > for i=1,7 do > curses.init_pair(i, 0, i) > end > > while true do > render(Window) > update(Window) > end >end - __teliva_timestamp: >Thu Feb 17 20:01:19 2022 doc:blurb: >Example app: counter widget > >Look at the menu below to see what keyboard shortcuts are available. ================================================ FILE: doc/contents.html ================================================ Teliva Reference Manual - contents

Teliva Reference Manual

This reference manual for Teliva is based on the official definition of the Lua language.

Please support Lua by donating or purchasing:

Lua 5.1 Reference Manual
by R. Ierusalimschy, L. H. de Figueiredo, W. Celes
Lua.org, August 2006
ISBN 85-903798-3-3

start · contents · index · other versions


Copyright © 2006–2012 Lua.org, PUC-Rio. Freely available under the terms of the Lua license.

Contents

Index

Lua functions

_G
_VERSION

assert
collectgarbage
error
getfenv
getmetatable
ipairs
next
pairs
print
rawequal
rawget
rawset
select
setfenv
setmetatable
tonumber
tostring
type
unpack

coroutine.create
coroutine.resume
coroutine.running
coroutine.status
coroutine.wrap
coroutine.yield

debug.debug
debug.getfenv
debug.gethook
debug.getinfo
debug.getlocal
debug.getmetatable
debug.getregistry
debug.getupvalue
debug.setfenv
debug.sethook
debug.setlocal
debug.setmetatable
debug.setupvalue
debug.traceback

 

file:close
file:flush
file:lines
file:read
file:seek
file:setvbuf
file:write

io.close
io.open
io.tmpfile
io.type

math.abs
math.acos
math.asin
math.atan
math.atan2
math.ceil
math.cos
math.cosh
math.deg
math.exp
math.floor
math.fmod
math.frexp
math.huge
math.ldexp
math.log
math.log10
math.max
math.min
math.modf
math.pi
math.pow
math.rad
math.random
math.randomseed
math.sin
math.sinh
math.sqrt
math.tan
math.tanh

os.clock
os.date
os.difftime
os.exit
os.remove
os.rename
os.setlocale
os.time
os.tmpname

string.byte
string.char
string.dump
string.find
string.format
string.gmatch
string.gsub
string.len
string.lower
string.match
string.rep
string.reverse
string.sub
string.upper

table.concat
table.insert
table.maxn
table.remove
table.sort

================================================ FILE: doc/lua.css ================================================ body { color: #000000 ; background-color: #FFFFFF ; font-family: Helvetica, Arial, sans-serif ; text-align: justify ; margin: auto ; width: 50em ; } h1, h2, h3, h4 { font-family: Verdana, Geneva, sans-serif ; font-weight: normal ; font-style: italic ; } h2 { padding-top: 0.4em ; padding-bottom: 0.4em ; padding-left: 30px ; padding-right: 30px ; margin-left: -30px ; background-color: #E0E0FF ; } h3 { padding-left: 0.5em ; border-left: solid #E0E0FF 1em ; } table h3 { padding-left: 0px ; border-left: none ; } a:link { color: #000080 ; background-color: inherit ; text-decoration: none ; } a:visited { background-color: inherit ; text-decoration: none ; } a:link:hover, a:visited:hover { color: #000080 ; background-color: #E0E0FF ; } a:link:active, a:visited:active { color: #FF0000 ; } hr { border: 0 ; height: 1px ; color: #a0a0a0 ; background-color: #a0a0a0 ; } :target { background-color: #F8F8F8 ; padding: 8px ; border: solid #a0a0a0 2px ; } .footer { color: gray ; font-size: small ; } input[type=text] { border: solid #a0a0a0 2px ; border-radius: 2em ; -moz-border-radius: 2em ; background-image: url('images/search.png') ; background-repeat: no-repeat; background-position: 4px center ; padding-left: 20px ; height: 2em ; } .teliva { background-color: #f1a7fe; padding-left: 2px; padding-right: 2px; padding-top: 5px; padding-bottom: 2px; } ================================================ FILE: doc/manual.css ================================================ h3 code { font-family: inherit ; font-size: inherit ; } pre, code { font-size: 12pt ; } span.apii { float: right ; font-family: inherit ; font-style: normal ; font-size: small ; color: gray ; } p+h1, ul+h1 { padding-top: 0.4em ; padding-bottom: 0.4em ; padding-left: 30px ; margin-left: -30px ; background-color: #E0E0FF ; } .teliva { background-color: #f1a7fe; padding-left: 2px; padding-right: 2px; padding-top: 5px; padding-bottom: 2px; } ================================================ FILE: doc/manual.html ================================================ Teliva Reference Manual


Teliva Reference Manual

by Kartik Agaram

Based on Lua 5.1 by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes

Copyright © 2006–2012 Lua.org, PUC-Rio. Freely available under the terms of the Lua license.


contents · index · other versions

1 - Introduction

Teliva is a platform based on Lua for sandboxed software packaged with an environment for making changes to it. For a more detailed introduction, see the Readme.

Teliva is free software, and is provided as usual with no guarantees, as stated in its license.

This manual is based on the Lua manual. Lua features absent in Teliva should be absent in this manual as well. Features added to Teliva above and beyond Lua will be described in text like this.

For a discussion of the decisions behind the design of Lua, see the technical papers available at Lua's web site. For a detailed introduction to programming in Lua, see Roberto's book, Programming in Lua (Second Edition).

2 - The Language

This section describes the lexis, the syntax, and the semantics of Lua. In other words, this section describes which tokens are valid, how they can be combined, and what their combinations mean.

The language constructs will be explained using the usual extended BNF notation, in which {a} means 0 or more a's, and [a] means an optional a. Non-terminals are shown like non-terminal, keywords are shown like kword, and other terminal symbols are shown like `=´. The complete syntax of Lua can be found in §8 at the end of this manual.

2.1 - Lexical Conventions

Names (also called identifiers) in Lua can be any string of letters, digits, and underscores, not beginning with a digit. This coincides with the definition of names in most languages. (The definition of letter depends on the current locale: any character considered alphabetic by the current locale can be used in an identifier.) Identifiers are used to name variables and table fields.

The following keywords are reserved and cannot be used as names:

     and       break     do        else      elseif
     end       false     for       function  if
     in        local     nil       not       or
     repeat    return    then      true      until     while

Lua is a case-sensitive language: and is a reserved word, but And and AND are two different, valid names. As a convention, names starting with an underscore followed by uppercase letters (such as _VERSION) are reserved for internal global variables used by Lua.

The following strings denote other tokens:

     +     -     *     /     %     ^     #
     ==    ~=    <=    >=    <     >     =
     (     )     {     }     [     ]
     ;     :     ,     .     ..    ...

Literal strings can be delimited by matching single or double quotes, and can contain the following C-like escape sequences: '\a' (bell), '\b' (backspace), '\f' (form feed), '\n' (newline), '\r' (carriage return), '\t' (horizontal tab), '\v' (vertical tab), '\\' (backslash), '\"' (quotation mark [double quote]), and '\'' (apostrophe [single quote]). Moreover, a backslash followed by a real newline results in a newline in the string. A character in a string can also be specified by its numerical value using the escape sequence \ddd, where ddd is a sequence of up to three decimal digits. (Note that if a numerical escape is to be followed by a digit, it must be expressed using exactly three digits.) Strings in Lua can contain any 8-bit value, including embedded zeros, which can be specified as '\0'.

Literal strings can also be defined using a long format enclosed by long brackets. We define an opening long bracket of level n as an opening square bracket followed by n equal signs followed by another opening square bracket. So, an opening long bracket of level 0 is written as [[, an opening long bracket of level 1 is written as [=[, and so on. A closing long bracket is defined similarly; for instance, a closing long bracket of level 4 is written as ]====]. A long string starts with an opening long bracket of any level and ends at the first closing long bracket of the same level. Literals in this bracketed form can run for several lines, do not interpret any escape sequences, and ignore long brackets of any other level. They can contain anything except a closing bracket of the proper level.

For convenience, when the opening long bracket is immediately followed by a newline, the newline is not included in the string. As an example, in a system using ASCII (in which 'a' is coded as 97, newline is coded as 10, and '1' is coded as 49), the five literal strings below denote the same string:

     a = 'alo\n123"'
     a = "alo\n123\""
     a = '\97lo\10\04923"'
     a = [[alo
     123"]]
     a = [==[
     alo
     123"]==]

A numerical constant can be written with an optional decimal part and an optional decimal exponent. Lua also accepts integer hexadecimal constants, by prefixing them with 0x. Examples of valid numerical constants are

     3   3.0   3.1416   314.16e-2   0.31416E1   0xff   0x56

A comment starts with a double hyphen (--) anywhere outside a string. If the text immediately after -- is not an opening long bracket, the comment is a short comment, which runs until the end of the line. Otherwise, it is a long comment, which runs until the corresponding closing long bracket. Long comments are frequently used to disable code temporarily.

2.2 - Values and Types

Lua is a dynamically typed language. This means that variables do not have types; only values do. There are no type definitions in the language. All values carry their own type.

All values in Lua are first-class values. This means that all values can be stored in variables, passed as arguments to other functions, and returned as results.

There are eight basic types in Lua: nil, boolean, number, string, function, userdata, thread, and table. Nil is the type of the value nil, whose main property is to be different from any other value; it usually represents the absence of a useful value. Boolean is the type of the values false and true. Both nil and false make a condition false; any other value makes it true. Number represents real (double-precision floating-point) numbers. (It is easy to build Lua interpreters that use other internal representations for numbers, such as single-precision float or long integers; see file luaconf.h.) String represents arrays of characters. Lua is 8-bit clean: strings can contain any 8-bit character, including embedded zeros ('\0') (see §2.1).

The type userdata is opaque to Teliva, and used by low-level code to manage details of C data stored in Lua variables. Unlike Lua, Teliva doesn't support creating or managing userdata since it doesn't permit extension by C libraries. This manual doesn't allude to userdata further.

The type thread represents independent threads of execution and it is used to implement coroutines (see §2.11). Do not confuse Lua threads with operating-system threads. Lua supports coroutines on all systems, even those that do not support threads.

The type table implements associative arrays, that is, arrays that can be indexed not only with numbers, but with any value (except nil). Tables can be heterogeneous; that is, they can contain values of all types (except nil). Tables are the sole data structuring mechanism in Lua; they can be used to represent ordinary arrays, symbol tables, sets, records, graphs, trees, etc. To represent records, Lua uses the field name as an index. The language supports this representation by providing a.name as syntactic sugar for a["name"]. There are several convenient ways to create tables in Lua (see §2.5.7).

Like indices, the value of a table field can be of any type (except nil). In particular, because functions are first-class values, table fields can contain functions. Thus tables can also carry methods (see §2.5.9).

Tables, functions and threads are objects: variables do not actually contain these values, only references to them. Assignment, parameter passing, and function returns always manipulate references to such values; these operations do not imply any kind of copy.

The library function type returns a string describing the type of a given value.

2.2.1 - Coercion

Lua provides automatic conversion between string and number values at run time. Any arithmetic operation applied to a string tries to convert this string to a number, following the usual conversion rules. Conversely, whenever a number is used where a string is expected, the number is converted to a string, in a reasonable format. For complete control over how numbers are converted to strings, use the format function from the string library (see string.format).

2.3 - Variables

Variables are places that store values. There are three kinds of variables in Lua: global variables, local variables, and table fields.

A single name can denote a global variable or a local variable (or a function's formal parameter, which is a particular kind of local variable):

	var ::= Name

Name denotes identifiers, as defined in §2.1.

Any variable is assumed to be global unless explicitly declared as a local (see §2.4.7). Local variables are lexically scoped: local variables can be freely accessed by functions defined inside their scope (see §2.6).

Before the first assignment to a variable, its value is nil.

Square brackets are used to index a table:

	var ::= prefixexp `[´ exp `]´

The meaning of accesses to global variables and table fields can be changed via metatables. An access to an indexed variable t[i] is equivalent to a call gettable_event(t,i). (See §2.8 for a complete description of the gettable_event function. This function is not defined or callable in Lua. We use it here only for explanatory purposes.)

The syntax var.Name is just syntactic sugar for var["Name"]:

	var ::= prefixexp `.´ Name

All global variables live as fields in ordinary Lua tables, called environment tables or simply environments (see §2.9). Each function has its own reference to an environment, so that all global variables in this function will refer to this environment table. When a function is created, it inherits the environment from the function that created it. To get the environment table of a Lua function, you call getfenv. To replace it, you call setfenv.

An access to a global variable x is equivalent to _env.x, which in turn is equivalent to

     gettable_event(_env, "x")

where _env is the environment of the running function. (See §2.8 for a complete description of the gettable_event function. This function is not defined or callable in Lua. Similarly, the _env variable is not defined in Lua. We use them here only for explanatory purposes.)

2.4 - Statements

Lua supports an almost conventional set of statements, similar to those in Pascal or C. This set includes assignments, control structures, function calls, and variable declarations.

2.4.1 - Chunks

The unit of execution of Lua is called a chunk. A chunk is simply a sequence of statements, which are executed sequentially. Each statement can be optionally followed by a semicolon:

	chunk ::= {stat [`;´]}

There are no empty statements and thus ';;' is not legal.

Lua handles a chunk as the body of an anonymous function with a variable number of arguments (see §2.5.9). As such, chunks can define local variables, receive arguments, and return values.

A chunk can be stored in a file or in a string inside the host program. To execute a chunk, Lua first pre-compiles the chunk into instructions for a virtual machine, and then it executes the compiled code with an interpreter for the virtual machine.

Chunks can also be pre-compiled into binary form; see program luac for details. Programs in source and compiled forms are interchangeable; Lua automatically detects the file type and acts accordingly.

2.4.2 - Blocks

A block is a list of statements; syntactically, a block is the same as a chunk:

	block ::= chunk

A block can be explicitly delimited to produce a single statement:

	stat ::= do block end

Explicit blocks are useful to control the scope of variable declarations. Explicit blocks are also sometimes used to add a return or break statement in the middle of another block (see §2.4.4).

2.4.3 - Assignment

Lua allows multiple assignments. Therefore, the syntax for assignment defines a list of variables on the left side and a list of expressions on the right side. The elements in both lists are separated by commas:

	stat ::= varlist `=´ explist
	varlist ::= var {`,´ var}
	explist ::= exp {`,´ exp}

Expressions are discussed in §2.5.

Before the assignment, the list of values is adjusted to the length of the list of variables. If there are more values than needed, the excess values are thrown away. If there are fewer values than needed, the list is extended with as many nil's as needed. If the list of expressions ends with a function call, then all values returned by that call enter the list of values, before the adjustment (except when the call is enclosed in parentheses; see §2.5).

The assignment statement first evaluates all its expressions and only then are the assignments performed. Thus the code

     i = 3
     i, a[i] = i+1, 20

sets a[3] to 20, without affecting a[4] because the i in a[i] is evaluated (to 3) before it is assigned 4. Similarly, the line

     x, y = y, x

exchanges the values of x and y, and

     x, y, z = y, z, x

cyclically permutes the values of x, y, and z.

The meaning of assignments to global variables and table fields can be changed via metatables. An assignment to an indexed variable t[i] = val is equivalent to settable_event(t,i,val). (See §2.8 for a complete description of the settable_event function. This function is not defined or callable in Lua. We use it here only for explanatory purposes.)

An assignment to a global variable x = val is equivalent to the assignment _env.x = val, which in turn is equivalent to

     settable_event(_env, "x", val)

where _env is the environment of the running function. (The _env variable is not defined in Lua. We use it here only for explanatory purposes.)

2.4.4 - Control Structures

The control structures if, while, and repeat have the usual meaning and familiar syntax:

	stat ::= while exp do block end
	stat ::= repeat block until exp
	stat ::= if exp then block {elseif exp then block} [else block] end

Lua also has a for statement, in two flavors (see §2.4.5).

The condition expression of a control structure can return any value. Both false and nil are considered false. All values different from nil and false are considered true (in particular, the number 0 and the empty string are also true).

In the repeatuntil loop, the inner block does not end at the until keyword, but only after the condition. So, the condition can refer to local variables declared inside the loop block.

The return statement is used to return values from a function or a chunk (which is just a function). Functions and chunks can return more than one value, and so the syntax for the return statement is

	stat ::= return [explist]

The break statement is used to terminate the execution of a while, repeat, or for loop, skipping to the next statement after the loop:

	stat ::= break

A break ends the innermost enclosing loop.

The return and break statements can only be written as the last statement of a block. If it is really necessary to return or break in the middle of a block, then an explicit inner block can be used, as in the idioms do return end and do break end, because now return and break are the last statements in their (inner) blocks.

2.4.5 - For Statement

The for statement has two forms: one numeric and one generic.

The numeric for loop repeats a block of code while a control variable runs through an arithmetic progression. It has the following syntax:

	stat ::= for Name `=´ exp `,´ exp [`,´ exp] do block end

The block is repeated for name starting at the value of the first exp, until it passes the second exp by steps of the third exp. More precisely, a for statement like

     for v = e1, e2, e3 do block end

is equivalent to the code:

     do
       local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3)
       if not (var and limit and step) then error() end
       while (step > 0 and var <= limit) or (step <= 0 and var >= limit) do
         local v = var
         block
         var = var + step
       end
     end

Note the following:

The generic for statement works over functions, called iterators. On each iteration, the iterator function is called to produce a new value, stopping when this new value is nil. The generic for loop has the following syntax:

	stat ::= for namelist in explist do block end
	namelist ::= Name {`,´ Name}

A for statement like

     for var_1, ···, var_n in explist do block end

is equivalent to the code:

     do
       local f, s, var = explist
       while true do
         local var_1, ···, var_n = f(s, var)
         var = var_1
         if var == nil then break end
         block
       end
     end

Note the following:

2.4.6 - Function Calls as Statements

To allow possible side-effects, function calls can be executed as statements:

	stat ::= functioncall

In this case, all returned values are thrown away. Function calls are explained in §2.5.8.

2.4.7 - Local Declarations

Local variables can be declared anywhere inside a block. The declaration can include an initial assignment:

	stat ::= local namelist [`=´ explist]

If present, an initial assignment has the same semantics of a multiple assignment (see §2.4.3). Otherwise, all variables are initialized with nil.

A chunk is also a block (see §2.4.1), and so local variables can be declared in a chunk outside any explicit block. The scope of such local variables extends until the end of the chunk.

The visibility rules for local variables are explained in §2.6.

2.5 - Expressions

The basic expressions in Lua are the following:

	exp ::= prefixexp
	exp ::= nil | false | true
	exp ::= Number
	exp ::= String
	exp ::= function
	exp ::= tableconstructor
	exp ::= `...´
	exp ::= exp binop exp
	exp ::= unop exp
	prefixexp ::= var | functioncall | `(´ exp `)´

Numbers and literal strings are explained in §2.1; variables are explained in §2.3; function definitions are explained in §2.5.9; function calls are explained in §2.5.8; table constructors are explained in §2.5.7. Vararg expressions, denoted by three dots ('...'), can only be used when directly inside a vararg function; they are explained in §2.5.9.

Binary operators comprise arithmetic operators (see §2.5.1), relational operators (see §2.5.2), logical operators (see §2.5.3), and the concatenation operator (see §2.5.4). Unary operators comprise the unary minus (see §2.5.1), the unary not (see §2.5.3), and the unary length operator (see §2.5.5).

Both function calls and vararg expressions can result in multiple values. If an expression is used as a statement (only possible for function calls (see §2.4.6)), then its return list is adjusted to zero elements, thus discarding all returned values. If an expression is used as the last (or the only) element of a list of expressions, then no adjustment is made (unless the call is enclosed in parentheses). In all other contexts, Lua adjusts the result list to one element, discarding all values except the first one.

Here are some examples:

     f()                -- adjusted to 0 results
     g(f(), x)          -- f() is adjusted to 1 result
     g(x, f())          -- g gets x plus all results from f()
     a,b,c = f(), x     -- f() is adjusted to 1 result (c gets nil)
     a,b = ...          -- a gets the first vararg parameter, b gets
                        -- the second (both a and b can get nil if there
                        -- is no corresponding vararg parameter)
     
     a,b,c = x, f()     -- f() is adjusted to 2 results
     a,b,c = f()        -- f() is adjusted to 3 results
     return f()         -- returns all results from f()
     return ...         -- returns all received vararg parameters
     return x,y,f()     -- returns x, y, and all results from f()
     {f()}              -- creates a list with all results from f()
     {...}              -- creates a list with all vararg parameters
     {f(), nil}         -- f() is adjusted to 1 result

Any expression enclosed in parentheses always results in only one value. Thus, (f(x,y,z)) is always a single value, even if f returns several values. (The value of (f(x,y,z)) is the first value returned by f or nil if f does not return any values.)

2.5.1 - Arithmetic Operators

Lua supports the usual arithmetic operators: the binary + (addition), - (subtraction), * (multiplication), / (division), % (modulo), and ^ (exponentiation); and unary - (negation). If the operands are numbers, or strings that can be converted to numbers (see §2.2.1), then all operations have the usual meaning. Exponentiation works for any exponent. For instance, x^(-0.5) computes the inverse of the square root of x. Modulo is defined as

     a % b == a - math.floor(a/b)*b

That is, it is the remainder of a division that rounds the quotient towards minus infinity.

2.5.2 - Relational Operators

The relational operators in Lua are

     ==    ~=    <     >     <=    >=

These operators always result in false or true.

Equality (==) first compares the type of its operands. If the types are different, then the result is false. Otherwise, the values of the operands are compared. Numbers and strings are compared in the usual way. Objects (tables, threads, and functions) are compared by reference: two objects are considered equal only if they are the same object. Every time you create a new object (a table, thread, or function), this new object is different from any previously existing object.

You can change the way that Lua compares tables by using the "eq" metamethod (see §2.8).

The conversion rules of §2.2.1 do not apply to equality comparisons. Thus, "0"==0 evaluates to false, and t[0] and t["0"] denote different entries in a table.

The operator ~= is exactly the negation of equality (==).

The order operators work as follows. If both arguments are numbers, then they are compared as such. Otherwise, if both arguments are strings, then their values are compared according to the current locale. Otherwise, Lua tries to call the "lt" or the "le" metamethod (see §2.8). A comparison a > b is translated to b < a and a >= b is translated to b <= a.

2.5.3 - Logical Operators

The logical operators in Lua are and, or, and not. Like the control structures (see §2.4.4), all logical operators consider both false and nil as false and anything else as true.

The negation operator not always returns false or true. The conjunction operator and returns its first argument if this value is false or nil; otherwise, and returns its second argument. The disjunction operator or returns its first argument if this value is different from nil and false; otherwise, or returns its second argument. Both and and or use short-cut evaluation; that is, the second operand is evaluated only if necessary. Here are some examples:

     10 or 20            --> 10
     10 or error()       --> 10
     nil or "a"          --> "a"
     nil and 10          --> nil
     false and error()   --> false
     false and nil       --> false
     false or nil        --> nil
     10 and 20           --> 20

(In this manual, --> indicates the result of the preceding expression.)

2.5.4 - Concatenation

The string concatenation operator in Lua is denoted by two dots ('..'). If both operands are strings or numbers, then they are converted to strings according to the rules mentioned in §2.2.1. Otherwise, the "concat" metamethod is called (see §2.8).

2.5.5 - The Length Operator

The length operator is denoted by the unary operator #. The length of a string is its number of bytes (that is, the usual meaning of string length when each character is one byte).

The length of a table t is defined to be any integer index n such that t[n] is not nil and t[n+1] is nil; moreover, if t[1] is nil, n can be zero. For a regular array, with non-nil values from 1 to a given n, its length is exactly that n, the index of its last value. If the array has "holes" (that is, nil values between other non-nil values), then #t can be any of the indices that directly precedes a nil value (that is, it may consider any such nil value as the end of the array).

2.5.6 - Precedence

Operator precedence in Lua follows the table below, from lower to higher priority:

     or
     and
     <     >     <=    >=    ~=    ==
     ..
     +     -
     *     /     %
     not   #     - (unary)
     ^

As usual, you can use parentheses to change the precedences of an expression. The concatenation ('..') and exponentiation ('^') operators are right associative. All other binary operators are left associative.

2.5.7 - Table Constructors

Table constructors are expressions that create tables. Every time a constructor is evaluated, a new table is created. A constructor can be used to create an empty table or to create a table and initialize some of its fields. The general syntax for constructors is

	tableconstructor ::= `{´ [fieldlist] `}´
	fieldlist ::= field {fieldsep field} [fieldsep]
	field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp
	fieldsep ::= `,´ | `;´

Each field of the form [exp1] = exp2 adds to the new table an entry with key exp1 and value exp2. A field of the form name = exp is equivalent to ["name"] = exp. Finally, fields of the form exp are equivalent to [i] = exp, where i are consecutive numerical integers, starting with 1. Fields in the other formats do not affect this counting. For example,

     a = { [f(1)] = g; "x", "y"; x = 1, f(x), [30] = 23; 45 }

is equivalent to

     do
       local t = {}
       t[f(1)] = g
       t[1] = "x"         -- 1st exp
       t[2] = "y"         -- 2nd exp
       t.x = 1            -- t["x"] = 1
       t[3] = f(x)        -- 3rd exp
       t[30] = 23
       t[4] = 45          -- 4th exp
       a = t
     end

If the last field in the list has the form exp and the expression is a function call or a vararg expression, then all values returned by this expression enter the list consecutively (see §2.5.8). To avoid this, enclose the function call or the vararg expression in parentheses (see §2.5).

The field list can have an optional trailing separator, as a convenience for machine-generated code.

2.5.8 - Function Calls

A function call in Lua has the following syntax:

	functioncall ::= prefixexp args

In a function call, first prefixexp and args are evaluated. If the value of prefixexp has type function, then this function is called with the given arguments. Otherwise, the prefixexp "call" metamethod is called, having as first parameter the value of prefixexp, followed by the original call arguments (see §2.8).

The form

	functioncall ::= prefixexp `:´ Name args

can be used to call "methods". A call v:name(args) is syntactic sugar for v.name(v,args), except that v is evaluated only once.

Arguments have the following syntax:

	args ::= `(´ [explist] `)´
	args ::= tableconstructor
	args ::= String

All argument expressions are evaluated before the call. A call of the form f{fields} is syntactic sugar for f({fields}); that is, the argument list is a single new table. A call of the form f'string' (or f"string" or f[[string]]) is syntactic sugar for f('string'); that is, the argument list is a single literal string.

As an exception to the free-format syntax of Lua, you cannot put a line break before the '(' in a function call. This restriction avoids some ambiguities in the language. If you write

     a = f
     (g).x(a)

Lua would see that as a single statement, a = f(g).x(a). So, if you want two statements, you must add a semi-colon between them. If you actually want to call f, you must remove the line break before (g).

A call of the form return functioncall is called a tail call. Lua implements proper tail calls (or proper tail recursion): in a tail call, the called function reuses the stack entry of the calling function. Therefore, there is no limit on the number of nested tail calls that a program can execute. However, a tail call erases any debug information about the calling function. Note that a tail call only happens with a particular syntax, where the return has one single function call as argument; this syntax makes the calling function return exactly the returns of the called function. So, none of the following examples are tail calls:

     return (f(x))        -- results adjusted to 1
     return 2 * f(x)
     return x, f(x)       -- additional results
     f(x); return         -- results discarded
     return x or f(x)     -- results adjusted to 1

2.5.9 - Function Definitions

The syntax for function definition is

	function ::= function funcbody
	funcbody ::= `(´ [parlist] `)´ block end

The following syntactic sugar simplifies function definitions:

	stat ::= function funcname funcbody
	stat ::= local function Name funcbody
	funcname ::= Name {`.´ Name} [`:´ Name]

The statement

     function f () body end

translates to

     f = function () body end

The statement

     function t.a.b.c.f () body end

translates to

     t.a.b.c.f = function () body end

The statement

     local function f () body end

translates to

     local f; f = function () body end

not to

     local f = function () body end

(This only makes a difference when the body of the function contains references to f.)

A function definition is an executable expression, whose value has type function. When Lua pre-compiles a chunk, all its function bodies are pre-compiled too. Then, whenever Lua executes the function definition, the function is instantiated (or closed). This function instance (or closure) is the final value of the expression. Different instances of the same function can refer to different external local variables and can have different environment tables.

Parameters act as local variables that are initialized with the argument values:

	parlist ::= namelist [`,´ `...´] | `...´

When a function is called, the list of arguments is adjusted to the length of the list of parameters, unless the function is a variadic or vararg function, which is indicated by three dots ('...') at the end of its parameter list. A vararg function does not adjust its argument list; instead, it collects all extra arguments and supplies them to the function through a vararg expression, which is also written as three dots. The value of this expression is a list of all actual extra arguments, similar to a function with multiple results. If a vararg expression is used inside another expression or in the middle of a list of expressions, then its return list is adjusted to one element. If the expression is used as the last element of a list of expressions, then no adjustment is made (unless that last expression is enclosed in parentheses).

As an example, consider the following definitions:

     function f(a, b) end
     function g(a, b, ...) end
     function r() return 1,2,3 end

Then, we have the following mapping from arguments to parameters and to the vararg expression:

     CALL            PARAMETERS
     
     f(3)             a=3, b=nil
     f(3, 4)          a=3, b=4
     f(3, 4, 5)       a=3, b=4
     f(r(), 10)       a=1, b=10
     f(r())           a=1, b=2
     
     g(3)             a=3, b=nil, ... -->  (nothing)
     g(3, 4)          a=3, b=4,   ... -->  (nothing)
     g(3, 4, 5, 8)    a=3, b=4,   ... -->  5  8
     g(5, r())        a=5, b=1,   ... -->  2  3

Results are returned using the return statement (see §2.4.4). If control reaches the end of a function without encountering a return statement, then the function returns with no results.

The colon syntax is used for defining methods, that is, functions that have an implicit extra parameter self. Thus, the statement

     function t.a.b.c:f (params) body end

is syntactic sugar for

     t.a.b.c.f = function (self, params) body end

2.6 - Visibility Rules

Lua is a lexically scoped language. The scope of variables begins at the first statement after their declaration and lasts until the end of the innermost block that includes the declaration. Consider the following example:

     x = 10                -- global variable
     do                    -- new block
       local x = x         -- new 'x', with value 10
       print(x)            --> 10
       x = x+1
       do                  -- another block
         local x = x+1     -- another 'x'
         print(x)          --> 12
       end
       print(x)            --> 11
     end
     print(x)              --> 10  (the global one)

Notice that, in a declaration like local x = x, the new x being declared is not in scope yet, and so the second x refers to the outside variable.

Because of the lexical scoping rules, local variables can be freely accessed by functions defined inside their scope. A local variable used by an inner function is called an upvalue, or external local variable, inside the inner function.

Notice that each execution of a local statement defines new local variables. Consider the following example:

     a = {}
     local x = 20
     for i=1,10 do
       local y = 0
       a[i] = function () y=y+1; return x+y end
     end

The loop creates ten closures (that is, ten instances of the anonymous function). Each of these closures uses a different y variable, while all of them share the same x.

2.7 - Error Handling

Errors returned by Teliva code should display on screen in red. Please report crashes or other behavior.

Lua code can explicitly generate an error by calling the error function. If you need to catch errors in Lua, you can use the pcall function.

2.8 - Metatables

Every value in Lua can have a metatable. This metatable is an ordinary Lua table that defines the behavior of the original value under certain special operations. You can change several aspects of the behavior of operations over a value by setting specific fields in its metatable. For instance, when a non-numeric value is the operand of an addition, Lua checks for a function in the field "__add" in its metatable. If it finds one, Lua calls this function to perform the addition.

We call the keys in a metatable events and the values metamethods. In the previous example, the event is "add" and the metamethod is the function that performs the addition.

You can query the metatable of any value through the getmetatable function.

You can replace the metatable of tables through the setmetatable function.

Tables have individual metatables (although multiple tables can share their metatables). Values of all other types share one single metatable per type; that is, there is one single metatable for all numbers, one for all strings, etc.

A metatable controls how an object behaves in arithmetic operations, order comparisons, concatenation, length operation, and indexing. For each of these operations Lua associates a specific key called an event. When Lua performs one of these operations over a value, it checks whether this value has a metatable with the corresponding event. If so, the value associated with that key (the metamethod) controls how Lua will perform the operation.

Metatables control the operations listed next. Each operation is identified by its corresponding name. The key for each operation is a string with its name prefixed by two underscores, '__'; for instance, the key for operation "add" is the string "__add". The semantics of these operations is better explained by a Lua function describing how the interpreter executes the operation.

The code shown here in Lua is only illustrative; the real behavior is hard coded in the interpreter and it is much more efficient than this simulation. All functions used in these descriptions (rawget, tonumber, etc.) are described in §5.1. In particular, to retrieve the metamethod of a given object, we use the expression

     metatable(obj)[event]

This should be read as

     rawget(getmetatable(obj) or {}, event)

That is, the access to a metamethod does not invoke other metamethods, and the access to objects with no metatables does not fail (it simply results in nil).

2.9 - Environments

Besides metatables, objects of types thread and function have another table associated with them, called their environment. Like metatables, environments are regular tables and multiple objects can share the same environment.

Threads are created sharing the environment of the creating thread. Nested Lua functions are created sharing the environment of the creating Lua function.

Environments associated with threads are called global environments. They are used as the default environment for threads.

Environments associated with Lua functions are used to resolve all accesses to global variables within the function (see §2.3). They are used as the default environment for nested Lua functions created by the function.

You can change the environment of a Lua function or the running thread by calling setfenv. You can get the environment of a Lua function or the running thread by calling getfenv.

2.10 - Garbage Collection

Lua performs automatic memory management. This means that you have to worry neither about allocating memory for new objects nor about freeing it when the objects are no longer needed. Lua manages memory automatically by running a garbage collector from time to time to collect all dead objects (that is, objects that are no longer accessible from Lua). All memory used by Lua is subject to automatic management: tables, functions, threads, strings, etc.

Lua implements an incremental mark-and-sweep collector. It uses two numbers to control its garbage-collection cycles: the garbage-collector pause and the garbage-collector step multiplier. Both use percentage points as units (so that a value of 100 means an internal value of 1).

The garbage-collector pause controls how long the collector waits before starting a new cycle. Larger values make the collector less aggressive. Values smaller than 100 mean the collector will not wait to start a new cycle. A value of 200 means that the collector waits for the total memory in use to double before starting a new cycle.

The step multiplier controls the relative speed of the collector relative to memory allocation. Larger values make the collector more aggressive but also increase the size of each incremental step. Values smaller than 100 make the collector too slow and can result in the collector never finishing a cycle. The default, 200, means that the collector runs at "twice" the speed of memory allocation.

Modifying these parameters requires changes to Teliva's C sources.

2.10.2 - Weak Tables

A weak table is a table whose elements are weak references. A weak reference is ignored by the garbage collector. In other words, if the only references to an object are weak references, then the garbage collector will collect this object.

A weak table can have weak keys, weak values, or both. A table with weak keys allows the collection of its keys, but prevents the collection of its values. A table with both weak keys and weak values allows the collection of both keys and values. In any case, if either the key or the value is collected, the whole pair is removed from the table. The weakness of a table is controlled by the __mode field of its metatable. If the __mode field is a string containing the character 'k', the keys in the table are weak. If __mode contains 'v', the values in the table are weak.

After you use a table as a metatable, you should not change the value of its __mode field. Otherwise, the weak behavior of the tables controlled by this metatable is undefined.

2.11 - Coroutines

Lua supports coroutines, also called collaborative multithreading. A coroutine in Lua represents an independent thread of execution. Unlike threads in multithread systems, however, a coroutine only suspends its execution by explicitly calling a yield function.

You create a coroutine with a call to coroutine.create. Its sole argument is a function that is the main function of the coroutine. The create function only creates a new coroutine and returns a handle to it (an object of type thread); it does not start the coroutine execution.

When you first call coroutine.resume, passing as its first argument a thread returned by coroutine.create, the coroutine starts its execution, at the first line of its main function. Extra arguments passed to coroutine.resume are passed on to the coroutine main function. After the coroutine starts running, it runs until it terminates or yields.

A coroutine can terminate its execution in two ways: normally, when its main function returns (explicitly or implicitly, after the last instruction); and abnormally, if there is an unprotected error. In the first case, coroutine.resume returns true, plus any values returned by the coroutine main function. In case of errors, coroutine.resume returns false plus an error message.

A coroutine yields by calling coroutine.yield. When a coroutine yields, the corresponding coroutine.resume returns immediately, even if the yield happens inside nested function calls (that is, not in the main function, but in a function directly or indirectly called by the main function). In the case of a yield, coroutine.resume also returns true, plus any values passed to coroutine.yield. The next time you resume the same coroutine, it continues its execution from the point where it yielded, with the call to coroutine.yield returning any extra arguments passed to coroutine.resume.

Like coroutine.create, the coroutine.wrap function also creates a coroutine, but instead of returning the coroutine itself, it returns a function that, when called, resumes the coroutine. Any arguments passed to this function go as extra arguments to coroutine.resume. coroutine.wrap returns all the values returned by coroutine.resume, except the first one (the boolean error code). Unlike coroutine.resume, coroutine.wrap does not catch errors; any error is propagated to the caller.

As an example, consider the following code:

     function foo (a)
       print("foo", a)
       return coroutine.yield(2*a)
     end
     
     co = coroutine.create(function (a,b)
           print("co-body", a, b)
           local r = foo(a+1)
           print("co-body", r)
           local r, s = coroutine.yield(a+b, a-b)
           print("co-body", r, s)
           return b, "end"
     end)
            
     print("main", coroutine.resume(co, 1, 10))
     print("main", coroutine.resume(co, "r"))
     print("main", coroutine.resume(co, "x", "y"))
     print("main", coroutine.resume(co, "x", "y"))

When you run it, it produces the following output:

     co-body 1       10
     foo     2
     
     main    true    4
     co-body r
     main    true    11      -9
     co-body x       y
     main    true    10      end
     main    false   cannot resume dead coroutine
Coroutines are powerful, but require some experience to use tastefully. Try to use them judiciously. If you find yourself juggling lots of coroutines and trying to decide which one to try to resume, you should probably be using tasks and channels instead. On the other hand, tasks have some cognitive overheads. You have to explicitly manage them with task.scheduler, and errors can sometimes be hard to track down. For simple generators that emit elements on demand, a coroutine is likely the best solution.

3 - C API

Unlike Lua, Teliva isn't intended to be modified at the C level. However, forks of Teliva are encouraged.

5 - Standard Libraries

The standard Lua libraries provide useful functions that are implemented directly in C. Some of these functions provide essential services to the language (e.g., type and getmetatable); others provide access to "outside" services (e.g., I/O); and others could be implemented in Lua itself, but are quite useful or have critical performance requirements that deserve an implementation in C (e.g., table.sort).

Currently, Lua has the following standard libraries:

Except for the basic and package libraries, each library provides all its functions as fields of a global table or as methods of its objects.

5.1 - Basic Primitives

The basic library provides some core functions to Lua. If you do not include this library in your application, you should check carefully whether you need to provide implementations for some of its facilities.


arg

A global variable containing any commandline arguments.


assert (v [, message])

Issues an error when the value of its argument v is false (i.e., nil or false); otherwise, returns all its arguments. message is an error message; when absent, it defaults to "assertion failed!"


collectgarbage ([opt [, arg]])

This function is a generic interface to the garbage collector. It performs different functions according to its first argument, opt:


error (message [, level])

Terminates the last protected function called and returns message as the error message. Function error never returns.

Usually, error adds some information about the error position at the beginning of the message. The level argument specifies how to get the error position. With level 1 (the default), the error position is where the error function was called. Level 2 points the error to where the function that called error was called; and so on. Passing a level 0 avoids the addition of error position information to the message.


_G

A global variable that holds the global environment (that is, _G._G = _G). Lua itself does not use this variable; changing its value does not affect any environment, nor vice-versa. (Use setfenv to change environments.)


getfenv ([f])

Returns the current environment in use by the function. f can be a Lua function or a number that specifies the function at that stack level: Level 1 is the function calling getfenv. If the given function is not a Lua function, or if f is 0, getfenv returns the global environment. The default for f is 1.


getmetatable (object)

If object does not have a metatable, returns nil. Otherwise, if the object's metatable has a "__metatable" field, returns the associated value. Otherwise, returns the metatable of the given object.


ipairs (t)

Returns three values: an iterator function, the table t, and 0, so that the construction

     for i,v in ipairs(t) do body end

will iterate over the pairs (1,t[1]), (2,t[2]), ···, up to the first integer key absent from the table.


next (table [, index])

Allows a program to traverse all fields of a table. Its first argument is a table and its second argument is an index in this table. next returns the next index of the table and its associated value. When called with nil as its second argument, next returns an initial index and its associated value. When called with the last index, or with nil in an empty table, next returns nil. If the second argument is absent, then it is interpreted as nil. In particular, you can use next(t) to check whether a table is empty.

The order in which the indices are enumerated is not specified, even for numeric indices. (To traverse a table in numeric order, use a numerical for or the ipairs function.)

The behavior of next is undefined if, during the traversal, you assign any value to a non-existent field in the table. You may however modify existing fields. In particular, you may clear existing fields.


pairs (t)

Returns three values: the next function, the table t, and nil, so that the construction

     for k,v in pairs(t) do body end

will iterate over all key–value pairs of table t.

See function next for the caveats of modifying the table during its traversal.


print (···)

Receives any number of arguments, and prints their values to stdout, using the tostring function to convert them to strings. print is not intended for formatted output, but only as a quick way to show a value, typically for debugging. For formatted output, use string.format.


rawequal (v1, v2)

Checks whether v1 is equal to v2, without invoking any metamethod. Returns a boolean.


rawget (table, index)

Gets the real value of table[index], without invoking any metamethod. table must be a table; index may be any value.


rawset (table, index, value)

Sets the real value of table[index] to value, without invoking any metamethod. table must be a table, index any value different from nil, and value any Lua value.

This function returns table.


select (index, ···)

If index is a number, returns all arguments after argument number index. Otherwise, index must be the string "#", and select returns the total number of extra arguments it received.


setfenv (f, table)

Sets the environment to be used by the given function. f can be a Lua function or a number that specifies the function at that stack level: Level 1 is the function calling setfenv. setfenv returns the given function.

As a special case, when f is 0 setfenv changes the environment of the running thread. In this case, setfenv returns no values.


setmetatable (table, metatable)

Sets the metatable for the given table. (You cannot change the metatable of other types.) If metatable is nil, removes the metatable of the given table. If the original metatable has a "__metatable" field, raises an error.

This function returns table.


tonumber (e [, base])

Tries to convert its argument to a number. If the argument is already a number or a string convertible to a number, then tonumber returns this number; otherwise, it returns nil.

An optional argument specifies the base to interpret the numeral. The base may be any integer between 2 and 36, inclusive. In bases above 10, the letter 'A' (in either upper or lower case) represents 10, 'B' represents 11, and so forth, with 'Z' representing 35. In base 10 (the default), the number can have a decimal part, as well as an optional exponent part (see §2.1). In other bases, only unsigned integers are accepted.


tostring (e)

Receives an argument of any type and converts it to a string in a reasonable format. For complete control of how numbers are converted, use string.format.

If the metatable of e has a "__tostring" field, then tostring calls the corresponding value with e as argument, and uses the result of the call as its result.


type (v)

Returns the type of its only argument, coded as a string. The possible results of this function are "nil" (a string, not the value nil), "number", "string", "boolean", "table", "function", "thread", and "userdata".


unpack (list [, i [, j]])

Returns the elements from the given table. This function is equivalent to
     return list[i], list[i+1], ···, list[j]

except that the above code can be written only for a fixed number of elements. By default, i is 1 and j is the length of the list, as defined by the length operator (see §2.5.5).


_VERSION

A global variable (not a function) that holds a string containing the current interpreter version. The current contents of this variable is "Lua 5.1".

5.2 - Coroutine Manipulation

The operations related to coroutines comprise a sub-library of the basic library and come inside the table coroutine. See §2.11 for a general description of coroutines.


coroutine.create (f)

Creates a new coroutine, with body f. f must be a Lua function. Returns this new coroutine, an object with type "thread".


coroutine.resume (co [, val1, ···])

Starts or continues the execution of coroutine co. The first time you resume a coroutine, it starts running its body. The values val1, ··· are passed as the arguments to the body function. If the coroutine has yielded, resume restarts it; the values val1, ··· are passed as the results from the yield.

If the coroutine runs without any errors, resume returns true plus any values passed to yield (if the coroutine yields) or any values returned by the body function (if the coroutine terminates). If there is any error, resume returns false plus the error message.


coroutine.running ()

Returns the running coroutine, or nil when called by the main thread.


coroutine.status (co)

Returns the status of coroutine co, as a string: "running", if the coroutine is running (that is, it called status); "suspended", if the coroutine is suspended in a call to yield, or if it has not started running yet; "normal" if the coroutine is active but not running (that is, it has resumed another coroutine); and "dead" if the coroutine has finished its body function, or if it has stopped with an error.


coroutine.wrap (f)

Creates a new coroutine, with body f. f must be a Lua function. Returns a function that resumes the coroutine each time it is called. Any arguments passed to the function behave as the extra arguments to resume. Returns the same values returned by resume, except the first boolean. In case of error, propagates the error.


coroutine.yield (···)

Suspends the execution of the calling coroutine. The coroutine cannot be running a primitive implemented in C, a metamethod, or an iterator. Any arguments to yield are passed as extra results to resume.

5.4 - String Manipulation

This library provides generic functions for string manipulation, such as finding and extracting substrings, and pattern matching. When indexing a string in Lua, the first character is at position 1 (not at 0, as in C). Indices are allowed to be negative and are interpreted as indexing backwards, from the end of the string. Thus, the last character is at position -1, and so on.

The string library provides all its functions inside the table string. It also sets a metatable for strings where the __index field points to the string table. Therefore, you can use the string functions in object-oriented style. For instance, string.byte(s, i) can be written as s:byte(i).

The string library assumes one-byte character encodings.


string.byte (s [, i [, j]])

Returns the internal numerical codes of the characters s[i], s[i+1], ···, s[j]. The default value for i is 1; the default value for j is i.

Note that numerical codes are not necessarily portable across platforms.


string.char (···)

Receives zero or more integers. Returns a string with length equal to the number of arguments, in which each character has the internal numerical code equal to its corresponding argument.

Note that numerical codes are not necessarily portable across platforms.


string.find (s, pattern [, init [, plain]])

Looks for the first match of pattern in the string s. If it finds a match, then find returns the indices of s where this occurrence starts and ends; otherwise, it returns nil. A third, optional numerical argument init specifies where to start the search; its default value is 1 and can be negative. A value of true as a fourth, optional argument plain turns off the pattern matching facilities, so the function does a plain "find substring" operation, with no characters in pattern being considered "magic". Note that if plain is given, then init must be given as well.

If the pattern has captures, then in a successful match the captured values are also returned, after the two indices.


string.format (formatstring, ···)

Returns a formatted version of its variable number of arguments following the description given in its first argument (which must be a string). The format string follows the same rules as the printf family of standard C functions. The only differences are that the options/modifiers *, l, L, n, p, and h are not supported and that there is an extra option, q. The q option formats a string in a form suitable to be safely read back by the Lua interpreter: the string is written between double quotes, and all double quotes, newlines, embedded zeros, and backslashes in the string are correctly escaped when written. For instance, the call
     string.format('%q', 'a string with "quotes" and \n new line')

will produce the string:

     "a string with \"quotes\" and \
      new line"

The options c, d, E, e, f, g, G, i, o, u, X, and x all expect a number as argument, whereas q and s expect a string.

This function does not accept string values containing embedded zeros, except as arguments to the q option.


string.gmatch (s, pattern)

Returns an iterator function that, each time it is called, returns the next captures from pattern over string s. If pattern specifies no captures, then the whole match is produced in each call.

As an example, the following loop

     s = "hello world from Lua"
     for w in string.gmatch(s, "%a+") do
       print(w)
     end

will iterate over all the words from string s, printing one per line. The next example collects all pairs key=value from the given string into a table:

     t = {}
     s = "from=world, to=Lua"
     for k, v in string.gmatch(s, "(%w+)=(%w+)") do
       t[k] = v
     end

For this function, a '^' at the start of a pattern does not work as an anchor, as this would prevent the iteration.


string.gsub (s, pattern, repl [, n])

Returns a copy of s in which all (or the first n, if given) occurrences of the pattern have been replaced by a replacement string specified by repl, which can be a string, a table, or a function. gsub also returns, as its second value, the total number of matches that occurred.

If repl is a string, then its value is used for replacement. The character % works as an escape character: any sequence in repl of the form %n, with n between 1 and 9, stands for the value of the n-th captured substring (see below). The sequence %0 stands for the whole match. The sequence %% stands for a single %.

If repl is a table, then the table is queried for every match, using the first capture as the key; if the pattern specifies no captures, then the whole match is used as the key.

If repl is a function, then this function is called every time a match occurs, with all captured substrings passed as arguments, in order; if the pattern specifies no captures, then the whole match is passed as a sole argument.

If the value returned by the table query or by the function call is a string or a number, then it is used as the replacement string; otherwise, if it is false or nil, then there is no replacement (that is, the original match is kept in the string).

Here are some examples:

     x = string.gsub("hello world", "(%w+)", "%1 %1")
     --> x="hello hello world world"
     
     x = string.gsub("hello world", "%w+", "%0 %0", 1)
     --> x="hello hello world"
     
     x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1")
     --> x="world hello Lua from"
     
     x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv)
     --> x="home = /home/roberto, user = roberto"
     
     local t = {name="lua", version="5.1"}
     x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
     --> x="lua-5.1.tar.gz"


string.len (s)

Receives a string and returns its length. The empty string "" has length 0. Embedded zeros are counted, so "a\000bc\000" has length 5.


string.lower (s)

Receives a string and returns a copy of this string with all uppercase letters changed to lowercase. All other characters are left unchanged. The definition of what an uppercase letter is depends on the current locale.


string.match (s, pattern [, init])

Looks for the first match of pattern in the string s. If it finds one, then match returns the captures from the pattern; otherwise it returns nil. If pattern specifies no captures, then the whole match is returned. A third, optional numerical argument init specifies where to start the search; its default value is 1 and can be negative.


string.rep (s, n)

Returns a string that is the concatenation of n copies of the string s.


string.reverse (s)

Returns a string that is the string s reversed.


string.sub (s, i [, j])

Returns the substring of s that starts at i and continues until j; i and j can be negative. If j is absent, then it is assumed to be equal to -1 (which is the same as the string length). In particular, the call string.sub(s,1,j) returns a prefix of s with length j, and string.sub(s, -i) returns a suffix of s with length i.


string.upper (s)

Receives a string and returns a copy of this string with all lowercase letters changed to uppercase. All other characters are left unchanged. The definition of what a lowercase letter is depends on the current locale.

5.4.1 - Patterns

Character Class:

A character class is used to represent a set of characters. The following combinations are allowed in describing a character class:

For all classes represented by single letters (%a, %c, etc.), the corresponding uppercase letter represents the complement of the class. For instance, %S represents all non-space characters.

The definitions of letter, space, and other character groups depend on the current locale. In particular, the class [a-z] may not be equivalent to %l.

Pattern Item:

A pattern item can be

Pattern:

A pattern is a sequence of pattern items. A '^' at the beginning of a pattern anchors the match at the beginning of the subject string. A '$' at the end of a pattern anchors the match at the end of the subject string. At other positions, '^' and '$' have no special meaning and represent themselves.

Captures:

A pattern can contain sub-patterns enclosed in parentheses; they describe captures. When a match succeeds, the substrings of the subject string that match captures are stored (captured) for future use. Captures are numbered according to their left parentheses. For instance, in the pattern "(a*(.)%w(%s*))", the part of the string matching "a*(.)%w(%s*)" is stored as the first capture (and therefore has number 1); the character matching "." is captured with number 2, and the part matching "%s*" has number 3.

As a special case, the empty capture () captures the current string position (a number). For instance, if we apply the pattern "()aa()" on the string "flaaap", there will be two captures: 3 and 5.

A pattern cannot contain embedded zeros. Use %z instead.

5.5 - Table Manipulation

This library provides generic functions for table manipulation. It provides all its functions inside the table table.

Most functions in the table library assume that the table represents an array or a list. For these functions, when we talk about the "length" of a table we mean the result of the length operator.


table.concat (table [, sep [, i [, j]]])

Given an array where all elements are strings or numbers, returns table[i]..sep..table[i+1] ··· sep..table[j]. The default value for sep is the empty string, the default for i is 1, and the default for j is the length of the table. If i is greater than j, returns the empty string.


table.insert (table, [pos,] value)

Inserts element value at position pos in table, shifting up other elements to open space, if necessary. The default value for pos is n+1, where n is the length of the table (see §2.5.5), so that a call table.insert(t,x) inserts x at the end of table t.


table.maxn (table)

Returns the largest positive numerical index of the given table, or zero if the table has no positive numerical indices. (To do its job this function does a linear traversal of the whole table.)


table.remove (table [, pos])

Removes from table the element at position pos, shifting down other elements to close the space, if necessary. Returns the value of the removed element. The default value for pos is n, where n is the length of the table, so that a call table.remove(t) removes the last element of table t.


table.sort (table [, comp])

Sorts table elements in a given order, in-place, from table[1] to table[n], where n is the length of the table. If comp is given, then it must be a function that receives two table elements, and returns true when the first is less than the second (so that not comp(a[i+1],a[i]) will be true after the sort). If comp is not given, then the standard Lua operator < is used instead.

The sort algorithm is not stable; that is, elements considered equal by the given order may have their relative positions changed by the sort.

5.6 - Mathematical Functions

This library is an interface to the standard C math library. It provides all its functions inside the table math.


math.abs (x)

Returns the absolute value of x.


math.acos (x)

Returns the arc cosine of x (in radians).


math.asin (x)

Returns the arc sine of x (in radians).


math.atan (x)

Returns the arc tangent of x (in radians).


math.atan2 (y, x)

Returns the arc tangent of y/x (in radians), but uses the signs of both parameters to find the quadrant of the result. (It also handles correctly the case of x being zero.)


math.ceil (x)

Returns the smallest integer larger than or equal to x.


math.cos (x)

Returns the cosine of x (assumed to be in radians).


math.cosh (x)

Returns the hyperbolic cosine of x.


math.deg (x)

Returns the angle x (given in radians) in degrees.


math.exp (x)

Returns the value ex.


math.floor (x)

Returns the largest integer smaller than or equal to x.


math.fmod (x, y)

Returns the remainder of the division of x by y that rounds the quotient towards zero.


math.frexp (x)

Returns m and e such that x = m2e, e is an integer and the absolute value of m is in the range [0.5, 1) (or zero when x is zero).


math.huge

The value HUGE_VAL, a value larger than or equal to any other numerical value.


math.ldexp (m, e)

Returns m2e (e should be an integer).


math.log (x)

Returns the natural logarithm of x.


math.log10 (x)

Returns the base-10 logarithm of x.


math.max (x, ···)

Returns the maximum value among its arguments.


math.min (x, ···)

Returns the minimum value among its arguments.


math.modf (x)

Returns two numbers, the integral part of x and the fractional part of x.


math.pi

The value of pi.


math.pow (x, y)

Returns xy. (You can also use the expression x^y to compute this value.)


math.rad (x)

Returns the angle x (given in degrees) in radians.


math.random ([m [, n]])

This function is an interface to the simple pseudo-random generator function rand provided by ANSI C. (No guarantees can be given for its statistical properties.)

When called without arguments, returns a uniform pseudo-random real number in the range [0,1). When called with an integer number m, math.random returns a uniform pseudo-random integer in the range [1, m]. When called with two integer numbers m and n, math.random returns a uniform pseudo-random integer in the range [m, n].


math.randomseed (x)

Sets x as the "seed" for the pseudo-random generator: equal seeds produce equal sequences of numbers.


math.sin (x)

Returns the sine of x (assumed to be in radians).


math.sinh (x)

Returns the hyperbolic sine of x.


math.sqrt (x)

Returns the square root of x. (You can also use the expression x^0.5 to compute this value.)


math.tan (x)

Returns the tangent of x (assumed to be in radians).


math.tanh (x)

Returns the hyperbolic tangent of x.

5.7 - File Input and Output Facilities

While Teliva supports the standard Lua primitives for managing files via handles, idiomatic Teliva uses some slightly different primitives that use a subtly different file object (name tbd).


start_reading (fs, filename)

This function opens a file exclusively for reading, and returns an object (NOT a file handle as in Lua) or nil on error. If the returned object is stored in variable f, f.read(format) will read from the file. Legal values for format are identical to file:read(format).

(The fs parameter is currently unused. It will be used to pass in fake file systems for tests.)


start_writing(fs, filename)

This function opens a file exclusively for writing, and returns an object (NOT a file handle as in Lua) or nil on error. If the result is stored in variable f, f.write(x) will write x to the file. f.close() will persist the changes and make them externally visible. All changes will be hidden until f.close().

(The fs parameter is currently unused. It will be used to pass in fake file systems for tests.)


The rest of this section describes Lua's standard primitives for File I/O.

Unless otherwise stated, all File I/O functions return nil on failure (plus an error message as a second result and a system-dependent error code as a third result) and some value different from nil on success.


io.close (file)

Equivalent to file:close().


io.open (filename [, mode])

This function opens a file, in the mode specified in the string mode. It returns a new file handle, or, in case of errors, nil plus an error message.

The mode string can be any of the following:

The mode string can also have a 'b' at the end, which is needed in some systems to open the file in binary mode. This string is exactly what is used in the standard C function fopen.

This function is sandboxed. It may fail if the computer owner's permissions disallow it.


io.tmpfile ()

Returns a handle for a temporary file. This file is opened in update mode and it is automatically removed when the program ends.


io.type (obj)

Checks whether obj is a valid file handle. Returns the string "file" if obj is an open file handle, "closed file" if obj is a closed file handle, or nil if obj is not a file handle.


file:close ()

Closes file. Note that files are automatically closed when their handles are garbage collected, but that takes an unpredictable amount of time to happen.


file:flush ()

Saves any written data to file.


file:lines ()

Returns an iterator function that, each time it is called, returns a new line from the file. Therefore, the construction

     for line in file:lines() do body end

will iterate over all lines of the file. (Unlike io.lines, this function does not close the file when the loop ends.)


file:read (···)

Reads the file file, according to the given formats, which specify what to read. For each format, the function returns a string (or a number) with the characters read, or nil if it cannot read data with the specified format. When called without formats, it uses a default format that reads the entire next line (see below).

The available formats are


file:seek ([whence] [, offset])

Sets and gets the file position, measured from the beginning of the file, to the position given by offset plus a base specified by the string whence, as follows:

In case of success, function seek returns the final file position, measured in bytes from the beginning of the file. If this function fails, it returns nil, plus a string describing the error.

The default value for whence is "cur", and for offset is 0. Therefore, the call file:seek() returns the current file position, without changing it; the call file:seek("set") sets the position to the beginning of the file (and returns 0); and the call file:seek("end") sets the position to the end of the file, and returns its size.


file:setvbuf (mode [, size])

Sets the buffering mode for an output file. There are three available modes:

For the last two cases, size specifies the size of the buffer, in bytes. The default is an appropriate size.


file:write (···)

Writes the value of each of its arguments to the file. The arguments must be strings or numbers. To write other values, use tostring or string.format before write.

5.8 - Operating System Facilities

This library is implemented through table os.


os.clock ()

Returns an approximation of the amount in seconds of CPU time used by the program.


os.date ([format [, time]])

Returns a string or a table containing date and time, formatted according to the given string format.

If the time argument is present, this is the time to be formatted (see the os.time function for a description of this value). Otherwise, date formats the current time.

If format starts with '!', then the date is formatted in Coordinated Universal Time. After this optional character, if format is the string "*t", then date returns a table with the following fields: year (four digits), month (1--12), day (1--31), hour (0--23), min (0--59), sec (0--61), wday (weekday, Sunday is 1), yday (day of the year), and isdst (daylight saving flag, a boolean).

If format is not "*t", then date returns the date as a string, formatted according to the same rules as the C function strftime.

When called without arguments, date returns a reasonable date and time representation that depends on the host system and on the current locale (that is, os.date() is equivalent to os.date("%c")).


os.difftime (t2, t1)

Returns the number of seconds from time t1 to time t2. In POSIX, Windows, and some other systems, this value is exactly t2-t1.


os.exit ([code])

Calls the C function exit, with an optional code, to terminate the host program. The default value for code is the success code.


os.remove (filename)

Deletes the file or directory with the given name. Directories must be empty to be removed. If this function fails, it returns nil, plus a string describing the error.


os.rename (oldname, newname)

Renames file or directory named oldname to newname. If this function fails, it returns nil, plus a string describing the error.

This function is sandboxed. It may fail if the computer owner's permissions disallow it.


os.setlocale (locale [, category])

Sets the current locale of the program. locale is a string specifying a locale; category is an optional string describing which category to change: "all", "collate", "ctype", "monetary", "numeric", or "time"; the default category is "all". The function returns the name of the new locale, or nil if the request cannot be honored.

If locale is the empty string, the current locale is set to an implementation-defined native locale. If locale is the string "C", the current locale is set to the standard C locale.

When called with nil as the first argument, this function only returns the name of the current locale for the given category.


os.time ([table])

Returns the current time when called without arguments, or a time representing the date and time specified by the given table. This table must have fields year, month, and day, and may have fields hour, min, sec, and isdst (for a description of these fields, see the os.date function).

The returned value is a number, whose meaning depends on your system. In POSIX, Windows, and some other systems, this number counts the number of seconds since some given start time (the "epoch"). In other systems, the meaning is not specified, and the number returned by time can be used only as an argument to date and difftime.


os.tmpname ()

Returns a string with a file name that can be used for a temporary file. The file must be explicitly opened before its use and explicitly removed when no longer needed.

On some systems (POSIX), this function also creates a file with that name, to avoid security risks. (Someone else might create the file with wrong permissions in the time between getting the name and creating the file.) You still have to open the file to use it and to remove it (even if you do not use it).

When possible, you may prefer to use io.tmpfile, which automatically removes the file when the program ends.

5.10 - Curses Window Facilities

Teliva includes curses facilities identical to Lua's lcurses library. As there, the top-level module is called curses. All apps start with the terminal window initialized using curses.initscr. Look at the sample apps for example usage.


curses.initscr ()

Initializes the current terminal to stop scrolling and enable moving the cursor. You shouldn't need to ever call this from Teliva; it's always called for you before an app is loaded.


curses.stdscr ()

Returns a window object for the current terminal. Most curses operations require a window. Windows are an app's gateway to both print to screen and read keys from keyboard. Teliva's template.tlv for new applications saves the result in a global called Window, so you should be able to avoid calling stdscr directly most of the time.

Curses supports multiple and nested windows. They haven't been tried yet in the context of Teliva, but they're expected to work. Please report your experience if you try them out.


window {}

Creates a fake window suitable for passing around in tests. The table passed in should have two keys: a kbd containing a keyboard, and a scr containing a screen.

This helper is implemented in template.tlv, so new apps should pick it up from there.


kbd (str)

Creates a fake keyboard suitable for passing into window with the characters in str already “typed in”.

This helper is implemented in template.tlv, so new apps should pick it up from there.


scr {}

Creates a fake screen suitable for passing into window. The table passed in should contain two keys: a height h and a width w.

This helper is implemented in template.tlv, so new apps should pick it up from there.


window:clear ()

Clears all prints in window.


window:refresh ()

Flushes all prints to window. Also redraws the Teliva menu.


window:addch (c)

Prints character c with the current attributes at the cursor in window. May not be visible until window:refresh is called.


window:mvaddch (y, x, c)

Moves window's cursor to (x, y) before printing character c to it with the current attributes. May not be visible until window:refresh is called.


window:addstr (str)

Prints string str with the current attributes at the cursor in window. May not be visible until window:refresh is called.


window:mvaddstr (y, x, str)

Moves window's cursor to (x, y) before printing string str to it with the current attributes. May not be visible until window:refresh is called.


window:getmaxyx ()

Returns window's height and width.


window:getyx ()

Returns window's cursor coordinates y and x.


window:attrset (attr)

Sets the current attributes for future prints to window. Attributes can be one of:

Since Lua 5.1 has no bitwise operations, this function currently only supports setting a single attribute.


window:attron (attr)

Adds the given attribute to the set of current attributes for future prints to window. For the list of available attributes see window:attrset.

Since Lua 5.1 has no bitwise operations, this function currently only supports adding a single attribute at a time.


window:attroff (attr)

Removes the given attribute from the set of current attributes for future prints to window. the list of available attributes see window:attrset.

Since Lua 5.1 has no bitwise operations, this function currently only supports removing a single attribute at a time.


curses.init_pair (i, fg, bg)

Initializes color pair i to (foreground, background). Now calls to curses.color_pair(i) will yield the attributes for that color pair.


curses.color_pair (i)

Returns attributes for a (foreground, background) pair of colors suitable to pass into window:attrset, window:attron and window:attroff.


window:getch ()

Returns a character from the keyboard. Waits for a key to be pressed by default, but this behavior can be changed by calling window:nodelay(true).

window:getch is the only supported way to get input from keyboard in Teliva, handling Teliva's menu and so on.


window:nodelay (on)

Forces window:getch() to be non-blocking.


Besides these, there are other primitives that have never been used in Teliva apps, but should still work. Please report if you try them out.

5.11 - Networking Facilities

Teliva includes the following well-known Lua libraries for networking:
  • LuaSocket, consisting of modules socket, http, url, headers, mime and ltn12.
  • LuaSec, consisting of modules https and ssl.
    Networking primitives are sandboxed, and may fail if the computer owner's permissions disallow it.
  • 5.12 - JSON Facilities

    Teliva includes the well-known json.lua library (module json). It also includes a variant in module jsonf that can read JSON from channels opened by start_reading.


    json.encode (value)

    Returns a string representing value encoded in JSON.

         json.encode({ 1, 2, 3, { x = 10 } }) -- Returns '[1,2,3,{"x":10}]'
    


    json.decode (str)

    Returns a value representing the JSON string str.

         json.decode('[1,2,3,{"x":10}]')      -- Returns { 1, 2, 3, { x = 10 } }
    


    jsonf.decode (f)

    Reads a value encoded in JSON from a file and returns it. f is the type of file object returned by start_reading (i.e. supporting f.read(format)). For example, suppose file foo contains '[1,2,3,{"x":10}]'. Then:

         local infile = start_reading(nil, 'foo')
         jsonf.decode(infile)                 -- Returns { 1, 2, 3, { x = 10 } }
    

    5.13 - Tasks and Channels

    Teliva includes the well-known lua-channels library in module task. See sieve.tlv for a basic example.


    task.spawn (fun, [...])

    Run fun as a coroutine with given parameters. Spawn tasks instead of just calling coroutine.create when you can't statically predict how your coroutines will transfer control to each other.


    task.scheduler ()

    Starts running any spawned tasks. Execution transfers to spawned tasks; this function only returns when there are no tasks left to run or when all tasks are blocked (deadlock).


    task.Channel:new ([size])

    Create a new channel with given size (which defaults to 0).


    channel:send (value)

    Write value to a channel. Blocks the current coroutine if the channel is already full. (Channels with size 0 always block if there isn't already a coroutine trying to recv from them.)


    channel:recv ()

    Read a value from a channel. Blocks the current coroutine if the channel is empty and there isn't already a coroutine trying to send to them.


    Besides these, there are other primitives that have never been used in Teliva apps, but should still work. Please report if you try them out.

    8 - The Complete Syntax of Lua

    Here is the complete syntax of Lua in extended BNF. (It does not describe operator precedences.)

    
    	chunk ::= {stat [`;´]} [laststat [`;´]]
    
    	block ::= chunk
    
    	stat ::=  varlist `=´ explist | 
    		 functioncall | 
    		 do block end | 
    		 while exp do block end | 
    		 repeat block until exp | 
    		 if exp then block {elseif exp then block} [else block] end | 
    		 for Name `=´ exp `,´ exp [`,´ exp] do block end | 
    		 for namelist in explist do block end | 
    		 function funcname funcbody | 
    		 local function Name funcbody | 
    		 local namelist [`=´ explist] 
    
    	laststat ::= return [explist] | break
    
    	funcname ::= Name {`.´ Name} [`:´ Name]
    
    	varlist ::= var {`,´ var}
    
    	var ::=  Name | prefixexp `[´ exp `]´ | prefixexp `.´ Name 
    
    	namelist ::= Name {`,´ Name}
    
    	explist ::= {exp `,´} exp
    
    	exp ::=  nil | false | true | Number | String | `...´ | function | 
    		 prefixexp | tableconstructor | exp binop exp | unop exp 
    
    	prefixexp ::= var | functioncall | `(´ exp `)´
    
    	functioncall ::=  prefixexp args | prefixexp `:´ Name args 
    
    	args ::=  `(´ [explist] `)´ | tableconstructor | String 
    
    	function ::= function funcbody
    
    	funcbody ::= `(´ [parlist] `)´ block end
    
    	parlist ::= namelist [`,´ `...´] | `...´
    
    	tableconstructor ::= `{´ [fieldlist] `}´
    
    	fieldlist ::= field {fieldsep field} [fieldsep]
    
    	field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp
    
    	fieldsep ::= `,´ | `;´
    
    	binop ::= `+´ | `-´ | `*´ | `/´ | `^´ | `%´ | `..´ | 
    		 `<´ | `<=´ | `>´ | `>=´ | `==´ | `~=´ | 
    		 and | or
    
    	unop ::= `-´ | not | `#´
    
    


    Last update: Mon Feb 13 18:54:19 BRST 2012 ================================================ FILE: gemini.tlv ================================================ # .tlv file generated by https://github.com/akkartik/teliva # You may edit it if you are careful; however, you may see cryptic errors if you # violate Teliva's assumptions. # # .tlv files are representations of Teliva programs. Teliva programs consist of # sequences of definitions. Each definition is a table of key/value pairs. Keys # and values are both strings. # # Lines in .tlv files always follow exactly one of the following forms: # - comment lines at the top of the file starting with '#' at column 0 # - beginnings of definitions starting with '- ' at column 0, followed by a # key/value pair # - key/value pairs consisting of ' ' at column 0, containing either a # spaceless value on the same line, or a multi-line value # - multiline values indented by more than 2 spaces, starting with a '>' # # If these constraints are violated, Teliva may unceremoniously crash. Please # report bugs at http://akkartik.name/contact - __teliva_timestamp: original str_helpers: >-- some string helpers from http://lua-users.org/wiki/StringIndexing > >-- index characters using [] >getmetatable('').__index = function(str,i) > if type(i) == 'number' then > return str:sub(i,i) > else > return string[i] > end >end > >-- ranges using (), selected bytes using {} >getmetatable('').__call = function(str,i,j) > if type(i)~='table' then > return str:sub(i,j) > else > local t={} > for k,v in ipairs(i) do > t[k]=str:sub(v,v) > end > return table.concat(t) > end >end > >-- iterate over an ordered sequence >function q(x) > if type(x) == 'string' then > return x:gmatch('.') > else > return ipairs(x) > end >end > >-- insert within string >function string.insert(str1, str2, pos) > return str1:sub(1,pos)..str2..str1:sub(pos+1) >end > >function string.remove(s, pos) > return s:sub(1,pos-1)..s:sub(pos+1) >end > >function string.pos(s, sub) > return string.find(s, sub, 1, true) -- plain=true to disable regular expressions >end > >-- TODO: backport utf-8 support from Lua 5.3 - __teliva_timestamp: original debugy: >debugy = 5 - __teliva_timestamp: original dbg: >-- helper for debug by print; overlay debug information towards the right >-- reset debugy every time you refresh screen >function dbg(window, s) > local oldy = 0 > local oldx = 0 > oldy, oldx = window:getyx() > window:mvaddstr(debugy, 60, s) > debugy = debugy+1 > window:mvaddstr(oldy, oldx, '') >end - __teliva_timestamp: original check: >function check(x, msg) > if x then > Window:addch('.') > else > print('F - '..msg) > print(' '..str(x)..' is false/nil') > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original check_eq: >function check_eq(x, expected, msg) > if eq(x, expected) then > Window:addch('.') > else > print('F - '..msg) > print(' expected '..str(expected)..' but got '..str(x)) > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original eq: >function eq(a, b) > if type(a) ~= type(b) then return false end > if type(a) == 'table' then > if #a ~= #b then return false end > for k, v in pairs(a) do > if b[k] ~= v then > return false > end > end > for k, v in pairs(b) do > if a[k] ~= v then > return false > end > end > return true > end > return a == b >end - __teliva_timestamp: original str: >-- smarter tostring >-- slow; used only for debugging >function str(x) > if type(x) == 'table' then > local result = '' > result = result..#x..'{' > for k, v in pairs(x) do > result = result..str(k)..'='..str(v)..', ' > end > result = result..'}' > return result > elseif type(x) == 'string' then > return '"'..x..'"' > end > return tostring(x) >end - __teliva_timestamp: original find_index: >function find_index(arr, x) > for n, y in ipairs(arr) do > if x == y then > return n > end > end >end - __teliva_timestamp: original trim: >function trim(s) > return s:gsub('^%s*', ''):gsub('%s*$', '') >end - __teliva_timestamp: original split: >function split(s, d) > result = {} > for match in (s..d):gmatch("(.-)"..d) do > table.insert(result, match); > end > return result >end - __teliva_timestamp: original clear: >function clear(lines) > while #lines > 0 do > table.remove(lines) > end >end - __teliva_timestamp: original Window: >Window = curses.stdscr() - __teliva_timestamp: original render_line: >function render_line(window, y, line) > window:mvaddstr(y, 0, '') > for i=1,line:len() do > window:addstr(line[i]) > end >end - __teliva_timestamp: original render_link: >function render_link(window, y, line) > local rendered_line = line:gsub('=>%s*%S*%s*', '') > if trim(rendered_line) == '' then > rendered_line = line > end > render_line(window, y, rendered_line) >end - __teliva_timestamp: original state: >state = { > lines={}, > history={}, > highlight_index=0, > source=false, -- show source (link urls, etc.) >} - __teliva_timestamp: original render_page: >function render_page(window) > local y = 0 > window:attron(curses.color_pair(6)) > print(state.url) > window:attroff(curses.color_pair(6)) > y = y+2 >--? dbg(window, state.highlight_index) > for i, line in pairs(state.lines) do > if not state.source and line:find('=> ') == 1 then > if state.highlight_index == 0 or i == state.highlight_index then > -- highlighted link > state.highlight_index = i -- TODO: ugly state update while rendering, just for first render after gemini_get > window:attron(curses.A_REVERSE) > render_link(window, y, line) > window:attroff(curses.A_REVERSE) > else > -- link > window:attron(curses.A_BOLD) > render_link(window, y, line) > window:attroff(curses.A_BOLD) > end > else > -- non-link > render_line(window, y, line) > end > y = y+1 > end >end - __teliva_timestamp: original render: >function render(window, lines) > window:clear() > render_page(window, lines) > curses.curs_set(0) > window:refresh() >end - __teliva_timestamp: original menu: >menu = { > {'Enter', 'go to highlight'}, > {'<-', 'back'}, > {'^g', 'enter url'}, > {'^u', 'view source'}, >} - __teliva_timestamp: original edit_line: >function edit_line(window) > local result = '' > local cursor = 1 > local screen_rows, screen_cols = window:getmaxyx() > menu = { > {'enter', 'submit'}, > {'^g', 'cancel'}, > {'^u', 'clear'}, > } > while true do > window:mvaddstr(screen_rows-1, 9, '') > window:clrtoeol() > window:mvaddstr(screen_rows-1, 9, result) > window:attron(curses.A_REVERSE) > -- window:refresh() > local key = window:getch() > window:attrset(curses.A_NORMAL) > if key >= 32 and key < 127 then > local screen_rows, screen_cols = window:getmaxyx() > if #result < screen_cols then > result = result:insert(string.char(key), cursor-1) > cursor = cursor+1 > end > elseif key == curses.KEY_LEFT then > if cursor > 1 then > cursor = cursor-1 > end > elseif key == curses.KEY_RIGHT then > if cursor <= #result then > cursor = cursor+1 > end > elseif key == curses.KEY_BACKSPACE then > if cursor > 1 then > cursor = cursor-1 > result = result:remove(cursor) > end > elseif key == 21 then -- ctrl-u > result = '' > cursor = 1 > elseif key == 10 then -- enter > return result > elseif key == 7 then -- ctrl-g > return nil > end > end >end - __teliva_timestamp: original is_link: >function is_link(line) > return line:find('=>%s*%S*%s*') == 1 >end - __teliva_timestamp: original next_link: >function next_link() > local new_index = state.highlight_index > while true do > new_index = new_index+1 > if new_index > #state.lines then return end > if is_link(state.lines[new_index]) then break end > end > state.highlight_index = new_index >end - __teliva_timestamp: original previous_link: >function previous_link() > local new_index = state.highlight_index > while true do > new_index = new_index - 1 > if new_index < 1 then return end > if is_link(state.lines[new_index]) then break end > end > state.highlight_index = new_index >end - __teliva_timestamp: original update: >function update(window) > local key = window:getch() > local screen_rows, screen_cols = window:getmaxyx() > if key == curses.KEY_DOWN then > next_link() > elseif key == curses.KEY_UP then > previous_link() > elseif key == curses.KEY_LEFT then > if #state.history > 1 then > table.remove(state.history) > gemini_get(table.remove(state.history)) > end > elseif key == 21 then -- ctrl-u > state.source = not state.source > elseif key == 10 then -- enter > local s, e, new_url = string.find(state.lines[state.highlight_index], '=>%s*(%S*)') > gemini_get(url.absolute(state.url, new_url)) > elseif key == 7 then -- ctrl-g > window:mvaddstr(screen_rows-2, 0, '') > window:clrtoeol() > window:mvaddstr(screen_rows-1, 0, '') > window:clrtoeol() > window:mvaddstr(screen_rows-1, 5, 'go: ') > curses.curs_set(2) > local old_menu = menu > local new_url = edit_line(window) > menu = old_menu > if new_url then > state.url = new_url > gemini_get(new_url) > end > curses.curs_set(0) > end >end - __teliva_timestamp: original init_colors: >function init_colors() > for i=0,7 do > curses.init_pair(i, i, -1) > end > curses.init_pair(8, 7, 0) > curses.init_pair(9, 7, 1) > curses.init_pair(10, 7, 2) > curses.init_pair(11, 7, 3) > curses.init_pair(12, 7, 4) > curses.init_pair(13, 7, 5) > curses.init_pair(14, 7, 6) > curses.init_pair(15, -1, 15) >end - __teliva_timestamp: original main: >function main() > Window:clear() > Window:refresh() > init_colors() > local lines = {} > local url = '' > if #arg > 0 then > state.url = arg[1] > lines = gemini_get(state.url) > end > while true do > render(Window, lines) > update(Window, lines) > end >end - __teliva_timestamp: original http_get: >function http_get(url) > -- https://stackoverflow.com/questions/42445423/luasocket-serveraccept-timeout-tcp > local parsed_url = socket.url.parse(url) > local tcp = socket.tcp() > tcp:connect(parsed_url.host, 80) > tcp:send('GET / HTTP/1.1\r\n') > -- http requires the Host header > tcp:send(string.format('Host: %s\r\n', parsed_url.host)) > tcp:send('\r\n') > -- tcp:receive('*a') doesn't seem to detect when a request is done > -- so we have to manage the size of the expected response > headers = {} > while true do > local s, status = tcp:receive() > if s == nil then break end > if s == '' then break end > local header, value = s:match('(.-): (.*)') > if header == nil then > print(s) > else > headers[header:lower()] = value > print(header, value) > end > end > local bytes_remaining = tonumber(headers['content-length']) > body = '' > while true do > local s, status = tcp:receive(bytes_remaining) > if s == nil then break end > body = body .. s > bytes_remaining = bytes_remaining - s:len() > if bytes_remaining <= 0 then break end > end > return body >end - __teliva_timestamp: original https_get: >-- http://notebook.kulchenko.com/programming/https-ssl-calls-with-lua-and-luasec >function https_get(url) > local parsed_url = socket.url.parse(url) > local params = { > mode = 'client', > protocol = 'any', > verify = 'none', -- I don't know what I'm doing > options = 'all', > } > local conn = socket.tcp() > conn:connect(parsed_url.host, parsed_url.port or 443) > conn, err = ssl.wrap(conn, params) > if conn == nil then > io.write(err) > os.exit(1) > end > conn:dohandshake() > > conn:send(url .. "\r\n") > local line, err = conn:receive() > return line or err >end - __teliva_timestamp: original parse_gemini_body: >function parse_gemini_body(conn, type) > if type == 'text/gemini' then > while true do > local line, err = conn:receive() > if line == nil then break end > table.insert(state.lines, line) > end > elseif type:sub(1, 5) == 'text/' then > while true do > local line, err = conn:receive() > if line == nil then break end > table.insert(state.lines, line) > end > end >end - __teliva_timestamp: original gemini_get: >-- http://notebook.kulchenko.com/programming/https-ssl-calls-with-lua-and-luasec >-- https://tildegit.org/solderpunk/gemini-demo-2 >-- returns an array of lines, containing either the body or just an error >function gemini_get(url) > if url:find("://") == nil then > url = "gemini://" .. url > end > local parsed_url = socket.url.parse(url) > local params = { > mode = 'client', > protocol = 'any', > verify = 'none', -- I don't know what I'm doing > options = 'all', > } > local conn = socket.tcp() > local conn2, err = conn:connect(parsed_url.host, parsed_url.port or 1965) > clear(state.lines) > state.highlight_index = 0 -- highlighted link not computed yet > if conn2 == nil then > table.insert(state.lines, err) > return > end > conn, err = ssl.wrap(conn, params) > if conn == nil then > table.insert(state.lines, err) > return > end > conn:dohandshake() > conn:send(url .. "\r\n") > local line, err = conn:receive() > if line == nil then > table.insert(state.lines, err) > return > end > local status, meta = line:match("(%S+) (%S+)") > if status[1] == '2' then > parse_gemini_body(conn, meta) > state.url = url > table.insert(state.history, url) > elseif status[1] == '3' then > gemini_get(socket.url.absolute(url, meta)) > elseif status[1] == '4' or line[1] == '5' then > table.insert(state.lines, 'Error: '..meta) > else > table.insert(state.lines, 'invalid response from server: '..line) > end >end - __teliva_timestamp: >Thu Feb 17 20:04:42 2022 doc:blurb: >A bare-bones browser for the Gemini protocol > >https://gemini.circumlunar.space > >A couple of good pages to try it out with: > $ src/teliva gemini.tlv gemini.circumlunar.space > $ src/teliva gemini.tlv gemini.conman.org > $ src/teliva gemini.tlv gemini.susa.net/cgi-bin/links.lua # shows 20 random links from Gemini space ================================================ FILE: graphviz.tlv ================================================ # .tlv file generated by https://github.com/akkartik/teliva # You may edit it if you are careful; however, you may see cryptic errors if you # violate Teliva's assumptions. # # .tlv files are representations of Teliva programs. Teliva programs consist of # sequences of definitions. Each definition is a table of key/value pairs. Keys # and values are both strings. # # Lines in .tlv files always follow exactly one of the following forms: # - comment lines at the top of the file starting with '#' at column 0 # - beginnings of definitions starting with '- ' at column 0, followed by a # key/value pair # - key/value pairs consisting of ' ' at column 0, containing either a # spaceless value on the same line, or a multi-line value # - multiline values indented by more than 2 spaces, starting with a '>' # # If these constraints are violated, Teliva may unceremoniously crash. Please # report bugs at http://akkartik.name/contact - __teliva_timestamp: original str_helpers: >-- some string helpers from http://lua-users.org/wiki/StringIndexing > >-- index characters using [] >getmetatable('').__index = function(str,i) > if type(i) == 'number' then > return str:sub(i,i) > else > return string[i] > end >end > >-- ranges using (), selected bytes using {} >getmetatable('').__call = function(str,i,j) > if type(i)~='table' then > return str:sub(i,j) > else > local t={} > for k,v in ipairs(i) do > t[k]=str:sub(v,v) > end > return table.concat(t) > end >end > >-- iterate over an ordered sequence >function q(x) > if type(x) == 'string' then > return x:gmatch('.') > else > return ipairs(x) > end >end > >-- insert within string >function string.insert(str1, str2, pos) > return str1:sub(1,pos)..str2..str1:sub(pos+1) >end > >function string.remove(s, pos) > return s:sub(1,pos-1)..s:sub(pos+1) >end > >function string.pos(s, sub) > return string.find(s, sub, 1, true) -- plain=true to disable regular expressions >end > >-- TODO: backport utf-8 support from Lua 5.3 - __teliva_timestamp: original debugy: >debugy = 5 - __teliva_timestamp: original dbg: >-- helper for debug by print; overlay debug information towards the right >-- reset debugy every time you refresh screen >function dbg(window, s) > local oldy = 0 > local oldx = 0 > oldy, oldx = window:getyx() > window:mvaddstr(debugy, 60, s) > debugy = debugy+1 > window:mvaddstr(oldy, oldx, '') >end - __teliva_timestamp: original check: >function check(x, msg) > if x then > Window:addch('.') > else > print('F - '..msg) > print(' '..str(x)..' is false/nil') > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original check_eq: >function check_eq(x, expected, msg) > if eq(x, expected) then > Window:addch('.') > else > print('F - '..msg) > print(' expected '..str(expected)..' but got '..str(x)) > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original eq: >function eq(a, b) > if type(a) ~= type(b) then return false end > if type(a) == 'table' then > if #a ~= #b then return false end > for k, v in pairs(a) do > if b[k] ~= v then > return false > end > end > for k, v in pairs(b) do > if a[k] ~= v then > return false > end > end > return true > end > return a == b >end - __teliva_timestamp: original str: >-- smarter tostring >-- slow; used only for debugging >function str(x) > if type(x) == 'table' then > local result = '' > result = result..#x..'{' > for k, v in pairs(x) do > result = result..str(k)..'='..str(v)..', ' > end > result = result..'}' > return result > elseif type(x) == 'string' then > return '"'..x..'"' > end > return tostring(x) >end - __teliva_timestamp: original find_index: >function find_index(arr, x) > for n, y in ipairs(arr) do > if x == y then > return n > end > end >end - __teliva_timestamp: original trim: >function trim(s) > return s:gsub('^%s*', ''):gsub('%s*$', '') >end - __teliva_timestamp: original split: >function split(s, d) > result = {} > for match in (s..d):gmatch("(.-)"..d) do > table.insert(result, match); > end > return result >end - __teliva_timestamp: original map: >-- only for arrays >function map(l, f) > result = {} > for _, x in ipairs(l) do > table.insert(result, f(x)) > end > return result >end - __teliva_timestamp: original reduce: >-- only for arrays >function reduce(l, f, init) > result = init > for _, x in ipairs(l) do > result = f(result, x) > end > return result >end - __teliva_timestamp: original filter: >function filter(h, f) > result = {} > for k, v in pairs(h) do > if f(k, v) then > result[k] = v > end > end > return result >end - __teliva_timestamp: original ifilter: >-- only for arrays >function ifilter(l, f) > result = {} > for _, x in ipairs(l) do > if f(x) then > table.insert(result, x) > end > end > return result >end - __teliva_timestamp: original sort_letters: >function sort_letters(s) > tmp = {} > for i=1,#s do > table.insert(tmp, s[i]) > end > table.sort(tmp) > local result = '' > for _, c in pairs(tmp) do > result = result..c > end > return result >end > >function test_sort_letters(s) > check_eq(sort_letters(''), '', 'test_sort_letters: empty') > check_eq(sort_letters('ba'), 'ab', 'test_sort_letters: non-empty') > check_eq(sort_letters('abba'), 'aabb', 'test_sort_letters: duplicates') >end - __teliva_timestamp: original count_letters: >-- TODO: handle unicode >function count_letters(s) > local result = {} > for i=1,s:len() do > local c = s[i] > if result[c] == nil then > result[c] = 1 > else > result[c] = result[c] + 1 > end > end > return result >end - __teliva_timestamp: original count: >-- turn an array of elements into a map from elements to their frequency >-- analogous to count_letters for non-strings >function count(a) > local result = {} > for i, v in ipairs(a) do > if result[v] == nil then > result[v] = 1 > else > result[v] = result[v] + 1 > end > end > return result >end - __teliva_timestamp: original union: >function union(a, b) > for k, v in pairs(b) do > a[k] = v > end > return a >end - __teliva_timestamp: original subtract: >-- set subtraction >function subtract(a, b) > for k, v in pairs(b) do > a[k] = nil > end > return a >end - __teliva_timestamp: original all: >-- universal quantifier on sets >function all(s, f) > for k, v in pairs(s) do > if not f(k, v) then > return false > end > end > return true >end - __teliva_timestamp: original to_array: >-- turn a set into an array >-- drops values >function to_array(h) > local result = {} > for k, _ in pairs(h) do > table.insert(result, k) > end > return result >end - __teliva_timestamp: original append: >-- concatenate list 'elems' into 'l', modifying 'l' in the process >function append(l, elems) > for i=1,#elems do > table.insert(l, elems[i]) > end >end - __teliva_timestamp: original prepend: >-- concatenate list 'elems' into the start of 'l', modifying 'l' in the process >function prepend(l, elems) > for i=1,#elems do > table.insert(l, i, elems[i]) > end >end - __teliva_timestamp: original all_but: >function all_but(x, idx) > if type(x) == 'table' then > local result = {} > for i, elem in ipairs(x) do > if i ~= idx then > table.insert(result,elem) > end > end > return result > elseif type(x) == 'string' then > if idx < 1 then return x:sub(1) end > return x:sub(1, idx-1) .. x:sub(idx+1) > else > error('all_but: unsupported type '..type(x)) > end >end > >function test_all_but() > check_eq(all_but('', 0), '', 'all_but: empty') > check_eq(all_but('abc', 0), 'abc', 'all_but: invalid low index') > check_eq(all_but('abc', 4), 'abc', 'all_but: invalid high index') > check_eq(all_but('abc', 1), 'bc', 'all_but: first index') > check_eq(all_but('abc', 3), 'ab', 'all_but: final index') > check_eq(all_but('abc', 2), 'ac', 'all_but: middle index') >end - __teliva_timestamp: original set: >function set(l) > local result = {} > for i, elem in ipairs(l) do > result[elem] = true > end > return result >end - __teliva_timestamp: original set_eq: >function set_eq(l1, l2) > return eq(set(l1), set(l2)) >end > >function test_set_eq() > check(set_eq({1}, {1}), 'set_eq: identical') > check(not set_eq({1, 2}, {1, 3}), 'set_eq: different') > check(set_eq({1, 2}, {2, 1}), 'set_eq: order') > check(set_eq({1, 2, 2}, {2, 1}), 'set_eq: duplicates') >end - __teliva_timestamp: original clear: >function clear(lines) > while #lines > 0 do > table.remove(lines) > end >end - __teliva_timestamp: original zap: >function zap(target, src) > clear(target) > append(target, src) >end - __teliva_timestamp: original mfactorial: >-- memoized version of factorial >-- doesn't memoize recursive calls, but may be good enough >mfactorial = memo1(factorial) - __teliva_timestamp: original factorial: >function factorial(n) > local result = 1 > for i=1,n do > result = result*i > end > return result >end - __teliva_timestamp: original memo1: >-- a higher-order function that takes a function of a single arg >-- (that never returns nil) >-- and returns a memoized version of it >function memo1(f) > local memo = {} > return function(x) > if memo[x] == nil then > memo[x] = f(x) > end > return memo[x] > end >end > >-- mfactorial doesn't seem noticeably faster >function test_memo1() > for i=0,30 do > check_eq(mfactorial(i), factorial(i), 'memo1 over factorial: '..str(i)) > end >end - __teliva_timestamp: original num_permutations: >-- number of permutations of n distinct objects, taken r at a time >function num_permutations(n, r) > return factorial(n)/factorial(n-r) >end > >-- mfactorial doesn't seem noticeably faster >function test_memo1() > for i=0,30 do > for j=0,i do > check_eq(num_permutations(i, j), mfactorial(i)/mfactorial(i-j), 'num_permutations memoizes: '..str(i)..'P'..str(j)) > end > end >end - __teliva_timestamp: original menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = {} - __teliva_timestamp: original Window: >Window = curses.stdscr() - __teliva_timestamp: original window: >-- constructor for fake screen and window >-- call it like this: >-- local w = window{ >-- kbd=kbd('abc'), >-- scr=scr{h=5, w=4}, >-- } >-- eventually it'll do everything a real ncurses window can >function window(h) > h.__index = h > setmetatable(h, h) > h.__index = function(table, key) > return rawget(h, key) > end > h.attrset = function(self, x) > self.scr.attrs = x > end > h.attron = function(self, x) > -- currently same as attrset since Lua 5.1 doesn't have bitwise operators > -- doesn't support multiple attrs at once >-- local old = self.scr.attrs >-- self.scr.attrs = old|x > self.scr.attrs = x > end > h.attroff = function(self, x) > -- currently borked since Lua 5.1 doesn't have bitwise operators > -- doesn't support multiple attrs at once >-- local old = self.scr.attrs >-- self.scr.attrs = old & (~x) > self.scr.attrs = curses.A_NORMAL > end > h.getch = function(self) > local c = table.remove(h.kbd, 1) > if c == nil then return c end > return string.byte(c) -- for verisimilitude with ncurses > end > h.addch = function(self, c) > local scr = self.scr > if c == '\n' then > scr.cursy = scr.cursy+1 > scr.cursx = 0 > return > end > if scr.cursy <= scr.h then > scr[scr.cursy][scr.cursx] = {data=c, attrs=scr.attrs} > scr.cursx = scr.cursx+1 > if scr.cursx > scr.w then > scr.cursy = scr.cursy+1 > scr.cursx = 1 > end > end > end > h.addstr = function(self, s) > for i=1,s:len() do > self:addch(s[i]) > end > end > h.mvaddch = function(self, y, x, c) > self.scr.cursy = y > self.scr.cursx = x > self:addch(c) > end > h.mvaddstr = function(self, y, x, s) > self.scr.cursy = y > self.scr.cursx = x > self:addstr(s) > end > h.clear = function(self) > clear_scr(self.scr) > end > h.refresh = function(self) > -- nothing > end > return h >end - __teliva_timestamp: original kbd: >function kbd(keys) > local result = {} > for i=1,keys:len() do > table.insert(result, keys[i]) > end > return result >end - __teliva_timestamp: original scr: >function scr(props) > props.cursx = 1 > props.cursy = 1 > clear_scr(props) > return props >end - __teliva_timestamp: original clear_scr: >function clear_scr(props) > props.cursy = 1 > props.cursx = 1 > for y=1,props.h do > props[y] = {} > for x=1,props.w do > props[y][x] = {data=' ', attrs=curses.A_NORMAL} > end > end > return props >end - __teliva_timestamp: original check_screen: >function check_screen(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > check_eq(window.scr[y][x].data, contents[i], message..'/'..y..','..x) > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end > >-- putting it all together, an example test of both keyboard and screen >function test_check_screen() > local lines = { > c='123', > d='234', > a='345', > b='456', > } > local w = window{ > kbd=kbd('abc'), > scr=scr{h=3, w=5}, > } > local y = 1 > while true do > local b = w:getch() > if b == nil then break end > w:mvaddstr(y, 1, lines[string.char(b)]) > y = y+1 > end > check_screen(w, '345 '.. > '456 '.. > '123 ', > 'test_check_screen') >end - __teliva_timestamp: original check_reverse: >function check_reverse(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > if contents[i] ~= ' ' then > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & curses.A_REVERSE, message..'/'..y..','..x) > check_eq(window.scr[y][x].attrs, curses.A_REVERSE, message..'/'..y..','..x) > else > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & (~curses.A_REVERSE), message..'/'..y..','..x) > check(window.scr[y][x].attrs ~= curses.A_REVERSE, message..'/'..y..','..x) > end > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end - __teliva_timestamp: original check_bold: >function check_bold(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > if contents[i] ~= ' ' then > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & curses.A_BOLD, message..'/'..y..','..x) > check_eq(window.scr[y][x].attrs, curses.A_BOLD, message..'/'..y..','..x) > else > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & (~curses.A_BOLD), message..'/'..y..','..x) > check(window.scr[y][x].attrs ~= curses.A_BOLD, message..'/'..y..','..x) > end > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end - __teliva_timestamp: original check_color: >-- check which parts of a screen have the given color_pair >function check_color(window, cp, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > if contents[i] ~= ' ' then > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & curses.color_pair(cp), message..'/'..y..','..x) > check_eq(window.scr[y][x].attrs, curses.color_pair(cp), message..'/'..y..','..x) > else > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & (~curses.A_BOLD), message..'/'..y..','..x) > check(window.scr[y][x].attrs ~= curses.color_pair(cp), message..'/'..y..','..x) > end > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end - __teliva_timestamp: original sep: >-- horizontal separator >function sep(window) > local y, _ = window:getyx() > window:mvaddstr(y+1, 0, '') > local _, cols = window:getmaxyx() > for col=1,cols do > window:addstr('_') > end >end - __teliva_timestamp: original render: >function render(window) > window:clear() > -- draw stuff to screen here > window:attron(curses.A_BOLD) > window:mvaddstr(1, 5, "example app") > window:attrset(curses.A_NORMAL) > for i=0,15 do > window:attrset(curses.color_pair(i)) > window:mvaddstr(3+i, 5, "========================") > end > window:refresh() >end - __teliva_timestamp: original update: >function update(window) > local key = window:getch() > -- process key here >end - __teliva_timestamp: original doc:blurb: >A REPL for queries about a graph in a .dot file. - __teliva_timestamp: original main: >function main() > if #arg == 0 then > Window:clear() > print('restart this app with the name of a .dot file') > Window:refresh() > while true do Window:getch(); end > end > Graph = read_dot_file(arg[1]) > > while true do > render(Window) > update(Window) > end >end - __teliva_timestamp: original Graph: >Graph = {} - __teliva_timestamp: original read_dot_file: >function read_dot_file(filename) > local graph = {} > local infile = start_reading(nil, filename) > if infile then > local chars = graphviz_buffered_reader(infile) > local tokens = graphviz_tokenizer(chars) > parse_graph(tokens, graph) > end > return graph >end - __teliva_timestamp: original graphviz_buffered_reader: >-- a stream of characters that can peek up to two characters ahead at a time >-- returns nil when there's nothing left to read >function graphviz_buffered_reader(infile) > return { > infile = infile, > peek = infile.read(1), > peek2 = infile.read(1), > read = function(self) > local result = self.peek > self.peek = self.peek2 > self.peek2 = self.infile.read(1) > return result > end, > } >end - __teliva_timestamp: original graphviz_tokenizer: >-- a stream of tokens that can peek up to one token at a time >-- returns nil when there's nothing left to read >function graphviz_tokenizer(chars) > return { > chars = chars, > peek = function(self) > if not self.buffer then > self.buffer = self:next_token() > end > return self.buffer > end, > read = function(self) > local result > if self.buffer then > result = self.buffer > self.buffer = nil > else > result = self:next_token() > end > return result > end, > next_token = function(self) > self:skip_whitespace_and_comments() > local c = self.chars.peek > if c == nil then return nil end > if string.pos('/*;,', c) then > -- should be skipped as comments > error('unexpected character '..c) > elseif string.pos('[]{}():=', c) then > -- single-char tokens > return self.chars:read() > elseif c == '"' then > return self:string() > elseif c == '<' then > error('html strings are not implemented yet') > elseif c == '-' then > if self.chars.peek2 == '-' or self.chars.peek2 == '>' then > return self:edgeop() > else > return self:numeral() > end > elseif string.pos('.0123456789', c) then > return self:numeral() > elseif string.match(c, '[%a_]') then > return self:identifier() > else > error('unexpected character '..str(c)) > end > end, > skip_whitespace_and_comments = function(self) > while true do > local c = self.chars.peek > if c == nil then > break -- end of chars > elseif string.match(c, '%s') then > self.chars:read() > elseif string.pos(',;', c) then > self.chars:read() > elseif c == '#' then > self.chars:read() -- skip > while self.chars:read() ~= '\n' do end > elseif c == '/' then > local c2 = self.chars.peek2 > if c2 == '*' then > self.chars:read() -- skip '/' > self.chars:read() -- skip '*' > while true do > if self.chars.peek == '*' and self.chars.peek2 == '/' then > self.chars:read() -- skip '*' > self.chars:read() -- skip '/' > break > end > end > elseif c2 == '/' then > self.chars:read() -- skip '/' > self.chars:read() -- skip '/' > while self.chars:read() ~= '\n' do end > else > error('unexpected character after "/": '..c) > end > else > break > end > end > end, > string = function(self) > assert(self.chars.peek == '"') > local result = self.chars:read() > while true do > local c = self.chars.peek > if c == nil then > error('unterminated string literal') > end > result = result..self.chars:read() > if c == '\\' then > result = result..self.chars:read() -- unconditionally read next char > elseif c == '"' then > break > end > end > return result > end, > numeral = function(self) > local result = '' > while true do > local c = self.chars.peek > if c == nil then > return result > elseif string.pos('-.0123456789', c) then > result = result..self.chars:read() > else > break > end > end > if string.match(self.chars.peek, '%w') then > error('invalid character after numeral '..result) > end > return result > end, > identifier = function(self) > local result = '' > while true do > local c = self.chars.peek > if c == nil then > error('unterminated string literal') > elseif string.match(c, '[%w_]') then > result = result..self.chars:read() > else > break > end > end > return result > end, > edgeop = function(self) > return self.chars:read()..self.chars:read() > end, > } >end > >function check_tokenizer(stream, expected, msg) > local infile = fake_file_stream(stream) > local chars = graphviz_buffered_reader(infile) > local tokens = graphviz_tokenizer(chars) > check_eq(tokens:read(), expected, msg) >end > >function test_graphviz_tokenizer() > check_tokenizer('123', '123', 'tokenizer: numeral') > check_tokenizer(' 123', '123', 'tokenizer: skips whitespace') > check_tokenizer('123 124', '123', 'tokenizer: numeral') > check_tokenizer('-12.3 124', '-12.3', 'tokenizer: numeral') > check_tokenizer('a123 124', 'a123', 'tokenizer: identifier') > check_tokenizer('"abc def" 124', '"abc def"', 'tokenizer: string') > check_tokenizer('', nil, 'tokenizer: eof') >end - __teliva_timestamp: original parse_graph: >-- https://graphviz.org/doc/info/lang.html >function parse_graph(tokens, graph) > local t = tokens:read() > if t == 'strict' then > t = tokens:read() > end > if t == 'graph' then > error('undirected graphs not supported just yet') > elseif t == 'digraph' then > return parse_directed_graph(tokens, graph) > else > error('parse_graph: unexpected token '..t) > end >end - __teliva_timestamp: >Fri Mar 18 16:52:39 2022 skip_attr_list: >function skip_attr_list(tokens) > while true do > local tok = tokens:read() > if tok == nil then > error('unterminated attr list; looked for "]" in vain') > end > if tok == ']' then break end > end >end - __teliva_timestamp: >Fri Mar 18 17:01:49 2022 parse_directed_graph: >function parse_directed_graph(tokens, graph) > tokens:read() -- skip name > assert(tokens:read() == '{') > while true do > if tokens:peek() == nil then > error('file not terminated with "}"') > end > if tokens:peek() == '}' then break end > local tok1 = tokens:read() > if tok1 == '[' then > skip_attr_list(tokens) > else > local tok2 = tokens:read() > if tok2 == '[' then > -- node_stmt > skip_attr_list(tokens) > -- otherwise ignore node declarations; > -- we'll assume the graph is fully connected > -- and we can focus on just edges > elseif tok2 == '->' then > -- edge_stmt > local tok3 = tokens:read() > if graph[tok1] == nil then > graph[tok1] = {} > end > graph[tok1][tok3] = true > elseif tok2 == '--' then > error('unexpected token "--" in digraph; edges should be directed using "->"') > elseif tok2 == '=' then > -- id '=' id > -- skip > tokens:read() > else > error('unexpected token '..tok2) > end > end > end > assert(tokens:read() == '}') >end - __teliva_timestamp: >Fri Mar 18 17:21:16 2022 Focus: >-- The focus is a set of nodes we're constantly running >-- certain queries against. >Focus = {} - __teliva_timestamp: >Fri Mar 18 17:21:40 2022 Graph: >-- The set of edges parsed from the given .dot file. >Graph = {} - __teliva_timestamp: >Fri Mar 18 17:29:25 2022 render_focus: >function render_focus(window) > window:attrset(curses.A_BOLD) > window:mvaddstr(5, 1, 'focus: ') > window:attrset(curses.A_NORMAL) > for _, node in ipairs(Focus) do > window:addstr(node) > window:addstr(' ') > end > > window:mvaddstr(8, 0, '') > local lines, cols = window:getmaxyx() > for col=1,cols do > window:addstr('_') > end >end - __teliva_timestamp: >Fri Mar 18 17:30:11 2022 render_queries_on_focus: >function render_queries_on_focus(window) > window:attrset(curses.A_BOLD) > window:mvaddstr(10, 1, '') > -- TODO >end - __teliva_timestamp: >Fri Mar 18 17:30:39 2022 render: >function render(window) > window:clear() > render_basic_stats(window) > render_focus(window) > render_queries_on_focus(window) > window:refresh() >end - __teliva_timestamp: >Fri Mar 18 17:35:20 2022 sources: >function sources(Graph) > local is_target = {} > for source, targets in pairs(Graph) do > for target, _ in pairs(targets) do > is_target[target] = true > end > end > local result = {} > for source, _ in pairs(Graph) do > if not is_target[source] then > table.insert(result, source) > end > end > return result >end - __teliva_timestamp: >Fri Mar 18 17:35:57 2022 render_basic_stats: >function render_basic_stats(window) > window:attrset(curses.A_BOLD) > window:mvaddstr(1, 1, 'sources: ') > window:attrset(curses.A_NORMAL) > local sources = sources(Graph) > for _, node in ipairs(sources) do > window:addstr(node) > window:addstr(' ') > end > window:mvaddstr(3, 0, '') > local lines, cols = window:getmaxyx() > for col=1,cols do > window:addstr('_') > end >end - __teliva_timestamp: >Fri Mar 18 17:51:18 2022 main: >function main() > if #arg == 0 then > Window:clear() > print('restart this app with the name of a .dot file') > Window:refresh() > while true do Window:getch(); end > end > for _, filename in ipairs(arg) do > read_dot_file(filename, Graph) > end > > while true do > render(Window) > update(Window) > end >end - __teliva_timestamp: >Fri Mar 18 17:51:32 2022 read_dot_file: >function read_dot_file(filename, graph) > local infile = start_reading(nil, filename) > if infile then > local chars = graphviz_buffered_reader(infile) > local tokens = graphviz_tokenizer(chars) > parse_graph(tokens, graph) > end >end - __teliva_timestamp: >Fri Mar 18 18:59:24 2022 count: >function count(h) > local result = 0 > for k, v in pairs(h) do > result = result+1 > end > return result >end - __teliva_timestamp: >Fri Mar 18 18:59:24 2022 num_nodes: >function num_nodes(Graph) > local nodes = {} > for k, v in pairs(Graph) do > nodes[k] = true > for k, v in pairs(v) do > nodes[k] = true > end > end > local result = 0 > for k, v in pairs(nodes) do > result = result+1 > end > return result >end - __teliva_timestamp: >Fri Mar 18 19:00:19 2022 render_basic_stats: >function render_basic_stats(window) > window:attrset(curses.A_BOLD) > window:mvaddstr(1, 1, 'sources: ') > window:attrset(curses.A_NORMAL) > local sources = sources(Graph) > for _, node in ipairs(sources) do > window:addstr(node) > window:addstr(' ') > end > window:attrset(curses.A_BOLD) > window:addstr('size: ') > window:attrset(curses.A_NORMAL) > window:addstr(tostring(num_nodes(Graph))) > window:addstr(' nodes') > window:mvaddstr(3, 0, '') > local lines, cols = window:getmaxyx() > for col=1,cols do > window:addstr('_') > end >end - __teliva_timestamp: >Fri Mar 18 19:01:49 2022 main: >function main() > if #arg == 0 then > Window:clear() > print('restart this app with the name of a .dot file') > Window:refresh() > while true do Window:getch(); end > end > for _, filename in ipairs(arg) do > read_dot_file(filename, Graph) > end > Focus = sources(Graph) > > while true do > render(Window) > update(Window) > end >end - __teliva_timestamp: >Fri Mar 18 19:09:56 2022 reachable: >function reachable(graph, node) > local reached = {} > local todo = {node} > while #todo > 0 do > local curr = table.remove(todo) > if reached[curr] == nil then > reached[curr] = true > local targets = graph[curr] > if targets then > for target, _ in pairs(graph[curr]) do > table.insert(todo, target) > end > end > end > end > return reached >end - __teliva_timestamp: >Fri Mar 18 20:27:16 2022 bold: >function bold(window, text) > window:attrset(curses.A_BOLD) > window:addstr(text) > window:attrset(curses.A_NORMAL) >end - __teliva_timestamp: >Fri Mar 18 20:30:39 2022 render_queries_on_focus: >function render_queries_on_focus(window) > render_reachable_sets(window) >end - __teliva_timestamp: >Fri Mar 18 20:30:39 2022 render_reachable_sets: >function render_reachable_sets(window) > local deps = {} > local needed_by = {} > for _, node in ipairs(Focus) do > deps[node] = reachable(Graph, node) > for dep, _ in pairs(deps[node]) do > if needed_by[dep] == nil then > needed_by[dep] = {} > end > append(needed_by[dep], {node}) > end > end > for k, v in ipairs(needed_by) do > table.sort(v) > end > window:mvaddstr(10, 0, '') > local sets = {Focus} -- queue > local done = {} > while #sets > 0 do > local from_nodes = table.remove(sets, 1) > if #from_nodes == 0 then break end > table.sort(from_nodes) > local key = table.concat(from_nodes) > if done[key] == nil then > done[key] = true > local y, x = window:getyx() > window:mvaddstr(y+2, 0, '') > window:attrset(curses.A_BOLD) > render_list(window, from_nodes) > window:attrset(curses.A_NORMAL) > window:addstr(' -> ') > render_set(window, filter(needed_by, function(node, users) return set_eq(users, from_nodes) end)) > for i, elem in ipairs(from_nodes) do > table.insert(sets, all_but(from_nodes, i)) > end > end > end >end - __teliva_timestamp: >Fri Mar 18 20:32:18 2022 render_list: >function render_list(window, l) > window:addstr('{') > for i, node in ipairs(l) do > if i > 1 then window:addstr(' ') end > window:addstr(node) > end > window:addstr('}') >end - __teliva_timestamp: >Fri Mar 18 20:32:18 2022 render_set: >function render_set(window, h) > window:addstr('(') > window:addstr(count(h)) > window:addstr(') ') > for node, _ in pairs(h) do > window:addstr(node) > window:addstr(' ') > end >end - __teliva_timestamp: >Sat Mar 19 09:19:10 2022 main: >function main() > if #arg == 0 then > Window:clear() > print('restart this app with the name of a .dot file') > Window:refresh() > while true do Window:getch(); end > end > for _, filename in ipairs(arg) do > read_dot_file(filename, Graph) > end > Focus = sources(Graph) > Nodes = toposort(Graph) > > while true do > render(Window) > update(Window) > end >end - __teliva_timestamp: >Sat Mar 19 09:32:33 2022 nodes: >function nodes(graph) > local result = {} > for n, deps in pairs(graph) do > result[n] = true > for n, _ in pairs(deps) do > result[n] = true > end > end > return result >end - __teliva_timestamp: >Sat Mar 19 16:27:32 2022 toposort: >-- stable sort of nodes in a graph >-- nodes always occur before all their dependencies >-- disconnected nodes are in alphabetical order >function toposort(graph) > -- non-map variables are arrays > -- result = leaves in graph > -- candidates = non-leaves > local result = {} > local resultMap = {} > local candidatesMap = nodes(graph) > local leavesMap = filter(candidatesMap, function(k, v) return graph[k] == nil end) > local leaves = to_array(leavesMap) > table.sort(leaves) > union(resultMap, leavesMap) > prepend(result, leaves) > subtract(candidatesMap, leavesMap) > > function in_result(x, _) return resultMap[x] end > function all_deps_in_result(k, _) return all(graph[k], in_result) end > while true do > local oldcount = count(candidatesMap) > if oldcount == 0 then break end > local inducteesMap = filter(candidatesMap, all_deps_in_result) > local inductees = to_array(inducteesMap) > table.sort(inductees) > union(resultMap, inducteesMap) > prepend(result, inductees) > subtract(candidatesMap, inducteesMap) > if oldcount == count(candidatesMap) then > error('toposort: graph is not connected') > end > end > return result >end - __teliva_timestamp: >Sat Mar 19 16:32:24 2022 render_focus: >function render_focus(window) > local y, _ = window:getyx() > window:mvaddstr(y+1, 0, '') > bold(window, 'focus: ') > for _, node in ipairs(Focus) do > window:addstr(node) > window:addstr(' ') > end > sep(window) >end - __teliva_timestamp: >Sat Mar 19 16:33:19 2022 render_basic_stats: >function render_basic_stats(window) > bold(window, tostring(#Nodes)..' nodes: ') > for i, node in ipairs(Nodes) do > window:attrset(curses.A_REVERSE) > window:addstr(i) > window:attrset(curses.A_NORMAL) > window:addstr(' ') > window:addstr(node) > window:addstr(' ') > end > sep(window) >end - __teliva_timestamp: >Sat Mar 19 16:35:34 2022 render_reachable_sets: >function render_reachable_sets(window) > local deps = {} > local needed_by = {} > for _, node in ipairs(Focus) do > deps[node] = reachable(Graph, node) > for dep, _ in pairs(deps[node]) do > if needed_by[dep] == nil then > needed_by[dep] = {} > end > append(needed_by[dep], {node}) > end > end > for k, v in ipairs(needed_by) do > table.sort(v) > end > local sets = {Focus} -- queue > local done = {} > while #sets > 0 do > local from_nodes = table.remove(sets, 1) > if #from_nodes == 0 then break end > table.sort(from_nodes) > local key = table.concat(from_nodes) > if done[key] == nil then > done[key] = true > local y, x = window:getyx() > window:mvaddstr(y+2, 0, '') > window:attrset(curses.A_BOLD) > render_list(window, from_nodes) > window:attrset(curses.A_NORMAL) > window:addstr(' -> ') > render_set(window, filter(needed_by, function(node, users) return set_eq(users, from_nodes) end)) > for i, elem in ipairs(from_nodes) do > table.insert(sets, all_but(from_nodes, i)) > end > end > end >end - __teliva_timestamp: >Sat Mar 19 21:05:05 2022 toposort: >-- stable sort of nodes in a graph >-- nodes always occur before all their dependencies >-- disconnected nodes are in alphabetical order >function toposort(graph) > -- non-map variables are arrays > -- result = leaves in graph > -- candidates = non-leaves > local inResultMap = {} > local candidatesMap = nodes(graph) > local leavesMap = filter(candidatesMap, function(k, v) return graph[k] == nil end) > local leaves = to_array(leavesMap) > table.sort(leaves) > union(inResultMap, leavesMap) > local result = {leaves} > subtract(candidatesMap, leavesMap) > > function in_result(x, _) return inResultMap[x] end > function all_deps_in_result(k, _) return all(graph[k], in_result) end > while true do > local oldcount = count(candidatesMap) > if oldcount == 0 then break end > local inducteesMap = filter(candidatesMap, all_deps_in_result) > local inductees = to_array(inducteesMap) > table.sort(inductees) > union(inResultMap, inducteesMap) > table.insert(result, 1, inductees) > subtract(candidatesMap, inducteesMap) > if oldcount == count(candidatesMap) then > error('toposort: graph is not connected') > end > end > return result >end - __teliva_timestamp: >Sat Mar 19 21:05:57 2022 render_basic_stats: >function render_basic_stats(window) > bold(window, tostring(#Nodes)..' nodes:') > local i = 1 > for _, stratum in ipairs(Nodes) do > window:addstr('\n ') > for _, node in ipairs(stratum) do > window:attrset(curses.A_REVERSE) > window:addstr(i) > window:attrset(curses.A_NORMAL) > window:addstr(' ') > window:addstr(node) > window:addstr(' ') > i = i+1 > end > end > sep(window) >end ================================================ FILE: hanoi.tlv ================================================ # .tlv file generated by https://github.com/akkartik/teliva # You may edit it if you are careful; however, you may see cryptic errors if you # violate Teliva's assumptions. # # .tlv files are representations of Teliva programs. Teliva programs consist of # sequences of definitions. Each definition is a table of key/value pairs. Keys # and values are both strings. # # Lines in .tlv files always follow exactly one of the following forms: # - comment lines at the top of the file starting with '#' at column 0 # - beginnings of definitions starting with '- ' at column 0, followed by a # key/value pair # - key/value pairs consisting of ' ' at column 0, containing either a # spaceless value on the same line, or a multi-line value # - multiline values indented by more than 2 spaces, starting with a '>' # # If these constraints are violated, Teliva may unceremoniously crash. Please # report bugs at http://akkartik.name/contact - __teliva_timestamp: original check: >function check(x, msg) > if x then > Window:addch('.') > else > print('F - '..msg) > print(' '..str(x)..' is false/nil') > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original check_eq: >function check_eq(x, expected, msg) > if eq(x, expected) then > Window:addch('.') > else > print('F - '..msg) > print(' expected '..str(expected)..' but got '..str(x)) > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original eq: >function eq(a, b) > if type(a) ~= type(b) then return false end > if type(a) == 'table' then > if #a ~= #b then return false end > for k, v in pairs(a) do > if b[k] ~= v then > return false > end > end > for k, v in pairs(b) do > if a[k] ~= v then > return false > end > end > return true > end > return a == b >end - __teliva_timestamp: original str: >-- smarter tostring >-- slow; used only for debugging >function str(x) > if type(x) == 'table' then > local result = '' > result = result..#x..'{' > for k, v in pairs(x) do > result = result..str(k)..'='..str(v)..', ' > end > result = result..'}' > return result > elseif type(x) == 'string' then > return '"'..x..'"' > end > return tostring(x) >end - __teliva_timestamp: original render: >function render(window) > window:clear() > local lines, cols = window:getmaxyx() > local line = math.floor(lines/2) > local col = math.floor(cols/4) > for i,t in ipairs(tower) do > render_tower(window, line, i*col, i, t) > end > window:refresh() >end - __teliva_timestamp: original lines: >function lines(window) > local lines, cols = window:getmaxyx() > return lines >end - __teliva_timestamp: original pop: >function pop(array) > return table.remove(array) >end - __teliva_timestamp: original Window: >Window = curses.stdscr() - __teliva_timestamp: original render_tower: >function render_tower(window, line, col, tower_index, tower) > window:attron(curses.A_BOLD) > window:mvaddch(line+2, col, string.char(96+tower_index)) > window:attroff(curses.A_BOLD) > window:attron(curses.color_pair(15)) > window:mvaddstr(line+1, col-6, " ") > window:attroff(curses.color_pair(15)) > for i, n in ipairs(tower) do > render_disk(window, line, col, n) > line = line - 1 > end > for i=1,5-len(tower)+1 do > window:attron(curses.color_pair(15)) > window:mvaddstr(line, col, " ") > window:attroff(curses.color_pair(15)) > line = line - 1 > end >end - __teliva_timestamp: original tower: >tower = {{6, 5, 4, 3, 2}, {}, {}} - __teliva_timestamp: original render_disk: >function render_disk(window, line, col, size) > col = col-size+1 > for i=1,size do > window:attron(curses.color_pair(size)) > window:mvaddstr(line, col, " ") > window:attroff(curses.color_pair(size)) > col = col+2 > end >end - __teliva_timestamp: original main: >function main() > curses.assume_default_colors(238, 139) > for i=1,7 do > curses.init_pair(i, 0, i) > end > curses.init_pair(15, 0, 250) -- tower frames > > while true do > render(Window) > update(Window) > end >end - __teliva_timestamp: original len: >function len(array) > local result = 0 > for k in pairs(array) do > result = result+1 > end > return result >end - __teliva_timestamp: original update: >function update(window) > window:mvaddstr(lines(window)-2, 5, "tower to remove top disk from? ") > local from = window:getch() - 96 > window:mvaddstr(lines(window)-1, 5, "tower to stack it on? ") > local to = window:getch() - 96 > make_move(from, to) >end - __teliva_timestamp: original make_move: >function make_move(from, to) > local disk = pop(tower[from]) > table.insert(tower[to], disk) >end - __teliva_timestamp: original cols: >function cols(window) > local lines, cols = window:getmaxyx() > return cols >end - __teliva_timestamp: >Thu Feb 17 20:07:06 2022 doc:blurb: >Single-player game: the Towers of Hanoi > >Move disks around from one tower A to tower B, under one constraint: a disk can never lie above a smaller disk. > >https://en.wikipedia.org/wiki/Tower_of_Hanoi ================================================ FILE: life.tlv ================================================ # .tlv file generated by https://github.com/akkartik/teliva # You may edit it if you are careful; however, you may see cryptic errors if you # violate Teliva's assumptions. # # .tlv files are representations of Teliva programs. Teliva programs consist of # sequences of definitions. Each definition is a table of key/value pairs. Keys # and values are both strings. # # Lines in .tlv files always follow exactly one of the following forms: # - comment lines at the top of the file starting with '#' at column 0 # - beginnings of definitions starting with '- ' at column 0, followed by a # key/value pair # - key/value pairs consisting of ' ' at column 0, containing either a # spaceless value on the same line, or a multi-line value # - multiline values indented by more than 2 spaces, starting with a '>' # # If these constraints are violated, Teliva may unceremoniously crash. Please # report bugs at http://akkartik.name/contact - __teliva_timestamp: original str_helpers: >-- some string helpers from http://lua-users.org/wiki/StringIndexing > >-- index characters using [] >getmetatable('').__index = function(str,i) > if type(i) == 'number' then > return str:sub(i,i) > else > return string[i] > end >end > >-- ranges using (), selected bytes using {} >getmetatable('').__call = function(str,i,j) > if type(i)~='table' then > return str:sub(i,j) > else > local t={} > for k,v in ipairs(i) do > t[k]=str:sub(v,v) > end > return table.concat(t) > end >end > >-- iterate over an ordered sequence >function q(x) > if type(x) == 'string' then > return x:gmatch('.') > else > return ipairs(x) > end >end > >-- insert within string >function string.insert(str1, str2, pos) > return str1:sub(1,pos)..str2..str1:sub(pos+1) >end > >function string.remove(s, pos) > return s:sub(1,pos-1)..s:sub(pos+1) >end > >function string.pos(s, sub) > return string.find(s, sub, 1, true) -- plain=true to disable regular expressions >end > >-- TODO: backport utf-8 support from Lua 5.3 - __teliva_timestamp: original debugy: >debugy = 5 - __teliva_timestamp: original dbg: >-- helper for debug by print; overlay debug information towards the right >-- reset debugy every time you refresh screen >function dbg(window, s) > local oldy = 0 > local oldx = 0 > oldy, oldx = window:getyx() > window:mvaddstr(debugy, 60, s) > debugy = debugy+1 > window:mvaddstr(oldy, oldx, '') >end - __teliva_timestamp: original check: >function check(x, msg) > if x then > Window:addch('.') > else > print('F - '..msg) > print(' '..str(x)..' is false/nil') > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original check_eq: >function check_eq(x, expected, msg) > if eq(x, expected) then > Window:addch('.') > else > print('F - '..msg) > print(' expected '..str(expected)..' but got '..str(x)) > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original eq: >function eq(a, b) > if type(a) ~= type(b) then return false end > if type(a) == 'table' then > if #a ~= #b then return false end > for k, v in pairs(a) do > if b[k] ~= v then > return false > end > end > for k, v in pairs(b) do > if a[k] ~= v then > return false > end > end > return true > end > return a == b >end - __teliva_timestamp: original str: >-- smarter tostring >-- slow; used only for debugging >function str(x) > if type(x) == 'table' then > local result = '' > result = result..#x..'{' > for k, v in pairs(x) do > result = result..str(k)..'='..str(v)..', ' > end > result = result..'}' > return result > elseif type(x) == 'string' then > return '"'..x..'"' > end > return tostring(x) >end - __teliva_timestamp: original menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = {} - __teliva_timestamp: original window: >-- constructor for fake screen and window >-- call it like this: >-- local w = window{ >-- kbd=kbd('abc'), >-- scr=scr{h=5, w=4}, >-- } >-- eventually it'll do everything a real ncurses window can >function window(h) > h.__index = h > setmetatable(h, h) > h.__index = function(table, key) > return rawget(h, key) > end > h.attrset = function(self, x) > self.scr.attrs = x > end > h.attron = function(self, x) > -- currently same as attrset since Lua 5.1 doesn't have bitwise operators > -- doesn't support multiple attrs at once >-- local old = self.scr.attrs >-- self.scr.attrs = old|x > self.scr.attrs = x > end > h.attroff = function(self, x) > -- currently borked since Lua 5.1 doesn't have bitwise operators > -- doesn't support multiple attrs at once >-- local old = self.scr.attrs >-- self.scr.attrs = old & (~x) > self.scr.attrs = curses.A_NORMAL > end > h.getch = function(self) > local c = table.remove(h.kbd, 1) > if c == nil then return c end > return string.byte(c) -- for verisimilitude with ncurses > end > h.addch = function(self, c) > local scr = self.scr > if c == '\n' then > scr.cursy = scr.cursy+1 > scr.cursx = 0 > return > end > if scr.cursy <= scr.h then > scr[scr.cursy][scr.cursx] = {data=c, attrs=scr.attrs} > scr.cursx = scr.cursx+1 > if scr.cursx > scr.w then > scr.cursy = scr.cursy+1 > scr.cursx = 1 > end > end > end > h.addstr = function(self, s) > for i=1,s:len() do > self:addch(s[i]) > end > end > h.mvaddch = function(self, y, x, c) > self.scr.cursy = y > self.scr.cursx = x > self:addch(c) > end > h.mvaddstr = function(self, y, x, s) > self.scr.cursy = y > self.scr.cursx = x > self:addstr(s) > end > h.clear = function(self) > clear_scr(self.scr) > end > h.refresh = function(self) > -- nothing > end > return h >end - __teliva_timestamp: original kbd: >function kbd(keys) > local result = {} > for i=1,keys:len() do > table.insert(result, keys[i]) > end > return result >end - __teliva_timestamp: original scr: >function scr(props) > props.cursx = 1 > props.cursy = 1 > clear_scr(props) > return props >end - __teliva_timestamp: original clear_scr: >function clear_scr(props) > props.cursy = 1 > props.cursx = 1 > for y=1,props.h do > props[y] = {} > for x=1,props.w do > props[y][x] = {data=' ', attrs=curses.A_NORMAL} > end > end > return props >end - __teliva_timestamp: original check_screen: >function check_screen(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > check_eq(window.scr[y][x].data, contents[i], message..'/'..y..','..x) > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end > >-- putting it all together, an example test of both keyboard and screen >function test_check_screen() > local lines = { > c='123', > d='234', > a='345', > b='456', > } > local w = window{ > kbd=kbd('abc'), > scr=scr{h=3, w=5}, > } > local y = 1 > while true do > local b = w:getch() > if b == nil then break end > w:mvaddstr(y, 1, lines[string.char(b)]) > y = y+1 > end > check_screen(w, '345 '.. > '456 '.. > '123 ', > 'test_check_screen') >end - __teliva_timestamp: original grid: >-- main data structure >grid = {} >for i=1,lines*4 do > grid[i] = {} > for j=1,cols*2 do > grid[i][j] = 0 > end >end - __teliva_timestamp: original Window: >Window = curses.stdscr() >-- animation-based app >Window:nodelay(true) >curses.curs_set(0) >lines, cols = Window:getmaxyx() - __teliva_timestamp: original grid_char: >-- grab a 4x2 chunk of grid >function grid_char(line, col) > result = {} > for l, row in ipairs({unpack(grid, (line-1)*4+1, line*4)}) do > result[l] = {unpack(row, (col-1)*2+1, col*2)} > end > return result >end - __teliva_timestamp: original print_grid_char: >function print_grid_char(window, x) > result = {} > for l, row in ipairs(x) do > for c, val in ipairs(row) do > window:mvaddstr(l, c, val) > end > end > return result >end - __teliva_timestamp: original glyph: >-- look up the braille pattern corresponding to a 4x2 chunk of grid >-- https://en.wikipedia.org/wiki/Braille_Patterns >-- not obviously programmatic because Unicode added 4x2 after 3x2 >glyph = { > 0x2800, 0x2801, 0x2802, 0x2803, 0x2804, 0x2805, 0x2806, 0x2807, 0x2840, 0x2841, 0x2842, 0x2843, 0x2844, 0x2845, 0x2846, 0x2847, > 0x2808, 0x2809, 0x280a, 0x280b, 0x280c, 0x280d, 0x280e, 0x280f, 0x2848, 0x2849, 0x284a, 0x284b, 0x284c, 0x284d, 0x284e, 0x284f, > 0x2810, 0x2811, 0x2812, 0x2813, 0x2814, 0x2815, 0x2816, 0x2817, 0x2850, 0x2851, 0x2852, 0x2853, 0x2854, 0x2855, 0x2856, 0x2857, > 0x2818, 0x2819, 0x281a, 0x281b, 0x281c, 0x281d, 0x281e, 0x281f, 0x2858, 0x2859, 0x285a, 0x285b, 0x285c, 0x285d, 0x285e, 0x285f, > 0x2820, 0x2821, 0x2822, 0x2823, 0x2824, 0x2825, 0x2826, 0x2827, 0x2860, 0x2861, 0x2862, 0x2863, 0x2864, 0x2865, 0x2866, 0x2867, > 0x2828, 0x2829, 0x282a, 0x282b, 0x282c, 0x282d, 0x282e, 0x282f, 0x2868, 0x2869, 0x286a, 0x286b, 0x286c, 0x286d, 0x286e, 0x286f, > 0x2830, 0x2831, 0x2832, 0x2833, 0x2834, 0x2835, 0x2836, 0x2837, 0x2870, 0x2871, 0x2872, 0x2873, 0x2874, 0x2875, 0x2876, 0x2877, > 0x2838, 0x2839, 0x283a, 0x283b, 0x283c, 0x283d, 0x283e, 0x283f, 0x2878, 0x2879, 0x287a, 0x287b, 0x287c, 0x287d, 0x287e, 0x287f, > > 0x2880, 0x2881, 0x2882, 0x2883, 0x2884, 0x2885, 0x2886, 0x2887, 0x28c0, 0x28c1, 0x28c2, 0x28c3, 0x28c4, 0x28c5, 0x28c6, 0x28c7, > 0x2888, 0x2889, 0x288a, 0x288b, 0x288c, 0x288d, 0x288e, 0x288f, 0x28c8, 0x28c9, 0x28ca, 0x28cb, 0x28cc, 0x28cd, 0x28ce, 0x28cf, > 0x2890, 0x2891, 0x2892, 0x2893, 0x2894, 0x2895, 0x2896, 0x2897, 0x28d0, 0x28d1, 0x28d2, 0x28d3, 0x28d4, 0x28d5, 0x28d6, 0x28d7, > 0x2898, 0x2899, 0x289a, 0x289b, 0x289c, 0x289d, 0x289e, 0x289f, 0x28d8, 0x28d9, 0x28da, 0x28db, 0x28dc, 0x28dd, 0x28de, 0x28df, > 0x28a0, 0x28a1, 0x28a2, 0x28a3, 0x28a4, 0x28a5, 0x28a6, 0x28a7, 0x28e0, 0x28e1, 0x28e2, 0x28e3, 0x28e4, 0x28e5, 0x28e6, 0x28e7, > 0x28a8, 0x28a9, 0x28aa, 0x28ab, 0x28ac, 0x28ad, 0x28ae, 0x28af, 0x28e8, 0x28e9, 0x28ea, 0x28eb, 0x28ec, 0x28ed, 0x28ee, 0x28ef, > 0x28b0, 0x28b1, 0x28b2, 0x28b3, 0x28b4, 0x28b5, 0x28b6, 0x28b7, 0x28f0, 0x28f1, 0x28f2, 0x28f3, 0x28f4, 0x28f5, 0x28f6, 0x28f7, > 0x28b8, 0x28b9, 0x28ba, 0x28bb, 0x28bc, 0x28bd, 0x28be, 0x28bf, 0x28f8, 0x28f9, 0x28fa, 0x28fb, 0x28fc, 0x28fd, 0x28fe, 0x28ff, >} - __teliva_timestamp: original utf8: >-- https://stackoverflow.com/questions/7983574/how-to-write-a-unicode-symbol-in-lua >function utf8(decimal) > local bytemarkers = { {0x7FF,192}, {0xFFFF,224}, {0x1FFFFF,240} } > if decimal<128 then return string.char(decimal) end > local charbytes = {} > for bytes,vals in ipairs(bytemarkers) do > if decimal<=vals[1] then > for b=bytes+1,2,-1 do > local mod = decimal%64 > decimal = (decimal-mod)/64 > charbytes[b] = string.char(128+mod) > end > charbytes[1] = string.char(vals[2]+decimal) > break > end > end > return table.concat(charbytes) >end - __teliva_timestamp: original grid_char_to_glyph_index: >-- convert a chunk of grid into a number >function grid_char_to_glyph_index(g) > return g[1][1] + g[2][1]*2 + g[3][1]*4 + g[4][1]*8 + > g[1][2]*16 + g[2][2]*32 + g[3][2]*64 + g[4][2]*128 + > 1 -- 1-indexing >end - __teliva_timestamp: original render: >function render(window) > window:clear() > window:attron(curses.color_pair(1)) > for line=1,lines do > for col=1,cols do > window:addstr(utf8(glyph[grid_char_to_glyph_index(grid_char(line, col))])) > end > end > window:attroff(curses.color_pair(1)) > window:refresh() >end - __teliva_timestamp: original state: >function state(line, col) > if line < 1 or line > #grid or col < 1 or col > #grid[1] then > return 0 > end > return grid[line][col] >end - __teliva_timestamp: original num_live_neighbors: >function num_live_neighbors(line, col) > return state(line-1, col-1) + state(line-1, col) + state(line-1, col+1) + > state(line, col-1) + state(line, col+1) + > state(line+1, col-1) + state(line+1, col) + state(line+1, col+1) >end - __teliva_timestamp: original step: >function step() > local new_grid = {} > for line=1,#grid do > new_grid[line] = {} > for col=1,#grid[1] do > local n = num_live_neighbors(line, col) > if n == 3 then > new_grid[line][col] = 1 > elseif n == 2 then > new_grid[line][col] = grid[line][col] > else > new_grid[line][col] = 0 > end > end > end > grid = new_grid >end - __teliva_timestamp: original sleep: >function sleep(a) > local sec = tonumber(os.clock() + a); > while (os.clock() < sec) do > end >end - __teliva_timestamp: original load_file: >function load_file(window, fs, filename) > local infile = start_reading(fs, filename) > if infile == nil then return end > local line_index = lines -- quarter of the way down in pixels > while true do > local line = infile.read() > if line == nil then break end > if line:sub(1,1) ~= '!' then -- comment; plaintext files can't have whitespace before comments > local col_index = cols > for c in line:gmatch(".") do > if c == '\r' then break end -- DOS line ending > if c == '.' then > grid[line_index][col_index] = 0 > else > grid[line_index][col_index] = 1 > end > col_index = col_index+1 > end > line_index = line_index+1 > end > end >end - __teliva_timestamp: original update: >menu = {{"arrow", "pan"}} > >function update(window, c) > if c == curses.KEY_LEFT then > for i=1,lines*4 do > for j=2,cols*2 do > grid[i][j-1] = grid[i][j] > end > grid[i][cols*2] = 0 > end > elseif c == curses.KEY_DOWN then > for i=lines*4-1,1,-1 do > for j=1,cols*2 do > grid[i+1][j] = grid[i][j] > end > end > for j=1,cols*2 do > grid[1][j] = 0 > end > elseif c == curses.KEY_UP then > for i=2,lines*4 do > for j=1,cols*2 do > grid[i-1][j] = grid[i][j] > end > end > for j=1,cols*2 do > grid[lines*4][j] = 0 > end > elseif c == curses.KEY_RIGHT then > for i=1,lines*4 do > for j=cols*2-1,1,-1 do > grid[i][j+1] = grid[i][j] > end > grid[i][1] = 0 > end > end >end - __teliva_timestamp: original main: >function main() > curses.init_pair(1, 22, 189) > > -- initialize grid based on commandline args > if (#arg == 0) then > -- by default, start from a deterministically random state > for i=1,lines*4 do > for j=1,cols*2 do > grid[i][j] = math.random(0, 1) > end > end > elseif arg[1] == "random" then > -- start from a non-deterministically random start state > math.randomseed(os.time()) > for i=1,lines*4 do > for j=1,cols*2 do > grid[i][j] = math.random(0, 1) > end > end > -- shortcuts for some common patterns > elseif arg[1] == "pentomino" then > -- https://www.conwaylife.com/wiki/Pentomino > grid[83][172] = 1 > grid[83][173] = 1 > grid[84][173] = 1 > grid[84][174] = 1 > grid[85][173] = 1 > elseif arg[1] == "glider" then > -- https://www.conwaylife.com/wiki/Glider > grid[5][4] = 1 > grid[6][5] = 1 > grid[7][3] = 1 > grid[7][4] = 1 > grid[7][5] = 1 > elseif arg[1] == "blinker" then > -- https://www.conwaylife.com/wiki/Blinker > grid[7][3] = 1 > grid[7][4] = 1 > grid[7][5] = 1 > elseif arg[1] == "block" then > -- https://www.conwaylife.com/wiki/Block > grid[5][4] = 1 > grid[5][5] = 1 > grid[6][4] = 1 > grid[6][5] = 1 > elseif arg[1] == "loaf" then > -- https://www.conwaylife.com/wiki/Loaf > grid[5][4] = 1 > grid[5][5] = 1 > grid[6][6] = 1 > grid[7][6] = 1 > grid[8][5] = 1 > grid[7][4] = 1 > grid[6][3] = 1 > else > -- Load a file in the standard "plaintext" format: https://www.conwaylife.com/wiki/Plaintext > -- > -- Each pattern page at https://www.conwaylife.com/wiki provides its > -- plaintext representation in a block called "Pattern Files" on the right. > -- > -- For example, check out the list of Important Patterns at > -- https://www.conwaylife.com/wiki/Category:Patterns_with_Catagolue_frequency_class_0 > load_file(Window, nil, arg[1]) > end > > -- main loop > while true do > render(Window) > c = Window:getch() > update(Window, c) > step() > end >end - __teliva_timestamp: >Thu Feb 17 19:58:19 2022 doc:blurb: >Conway's Game of Life > >To get around limitations of text mode we use the braille character set to render 8 cells per character. > >By default it initializes the space with a random state of cells. You can also start it up with .cells files from https://conwaylife.com/wiki, or with a few special names: > $ src/teliva life.tlv block > $ src/teliva life.tlv loaf > $ src/teliva life.tlv blinker > $ src/teliva life.tlv glider > $ src/teliva life.tlv pentomino ================================================ FILE: lisp.lua ================================================ -- atom types: -- nil -- true -- {num=3.4} -- {char='a'} -- {str='bc'} -- {sym='foo'} -- non-atom type: -- {car={num=3.4}, cdr=nil} -- -- should {} mean anything special? currently just '(nil) function atom(x) return x == nil or x.num or x.char or x.str or x.sym end function car(x) return x.car end function cdr(x) return x.cdr end function cons(x, y) return {car=x, cdr=y} end function iso(x, y) if x == nil then return y == nil end local done={} if done[x] then return done[x] == y end done[x] = y if atom(x) then if not atom(y) then return nil end for k, v in pairs(x) do if y[k] ~= v then return nil end end return true end for k, v in pairs(x) do if not iso(y[k], v) then return nil end end for k, v in pairs(y) do if not iso(x[k], v) then return nil end end return true end -- primitives; feel free to add more -- format: lisp name = lua function that implements it unary_functions = { atom=atom, car=car, cdr=cdr, } binary_functions = { cons=cons, iso=iso, } function lookup(env, s) if env[s] then return env[s] end if env.next then return lookup(env.next, s) end end function eval(x, env) function symeq(x, s) return x and x.sym == s end if x.sym then return lookup(env, x.sym) elseif atom(x) then return x -- otherwise x is a pair elseif symeq(x.car, 'quote') then return x.cdr elseif unary_functions[x.car.sym] then return eval_unary(x, env) elseif binary_functions[x.car.sym] then return eval_binary(x, env) -- special forms that don't always eval all their args elseif symeq(x.car, 'if') then return eval_if(x, env) elseif symeq(x.car.car, 'fn') then return eval_fn(x, env) elseif symeq(x.car.car, 'label') then return eval_label(x, env) end end function eval_unary(x, env) return unary_functions[x.car.sym](eval(x.cdr.car, env)) end function eval_binary(x, env) return binary_functions[x.car.sym](eval(x.cdr.car, env), eval(x.cdr.cdr.car, env)) end function eval_if(x, env) -- syntax: (if check b1 b2) local check = x.cdr.car local b1 = x.cdr.cdr.car local b2 = x.cdr.cdr.cdr.car if eval(check, env) then return eval(b1, env) else return eval(b2, env) end end function eval_fn(x, env) -- syntax: ((fn params body*) args*) local callee = x.car local args = x.cdr local params = callee.cdr.car local body = callee.cdr.cdr return eval_exprs(body, bind_env(params, args, env)) end function bind_env(params, args, env) if params == nil then return env end local result = {next=env} while true do result[params.car.sym] = eval(args.car, env) params = params.cdr args = args.cdr if params == nil then break end end return result end function eval_exprs(xs, env) local result = nil while xs do result = eval(xs.car, env) xs = xs.cdr end return result end function eval_label(x, env) -- syntax: ((label f (fn params body*)) args*) local callee = x.car local args = x.cdr local f = callee.cdr.car local fn = callee.cdr.cdr.car return eval({car=fn, cdr=args}, bind_env({f}, {callee}, env)) end -- testing function num(n) return {num=n} end function char(c) return {char=c} end function str(s) return {str=s} end function sym(s) return {sym=s} end function list(...) -- gotcha: no element in arg can be nil; that short-circuits the ipairs below local result = nil local curr = nil for _, x in ipairs({...}) do if curr == nil then result = {car=x} curr = result else curr.cdr = {car=x} curr = curr.cdr end end return result end function p(x) p2(x) print() end function p2(x) if x == nil then io.write('nil') elseif x == true then io.write('true') elseif x.num then io.write(x.num) elseif x.char then io.write("\\"..x.char) elseif x.str then io.write('"'..x.str..'"') elseif x.sym then io.write(x.sym) elseif x.cdr == nil then io.write('(') p2(x.car) io.write(')') elseif atom(x.cdr) then io.write('(') p2(x.car) io.write(' . ') p2(x.cdr) io.write(')') else io.write('(') while true do p2(x.car) x = x.cdr if x == nil then break end if atom(x) then io.write(' . ') p2(x) break end io.write(' ') end io.write(')') end end x = {num=3.4} p(x) p(cons(x, nil)) p(list(x)) p(iso(cons(x, nil), cons(x, nil))) p(iso(list(x), list(x))) p(iso(list(x, x), list(x))) p(iso(list(x, x), list(x, x))) p(iso(x, cons(x, nil))) p (list(sym("cons"), num(42), num(1))) p(eval(list(sym("cons"), num(42), num(1)), {})) -- ((fn () 42)) => 42 -- can't use list here because of the gotcha above assert(iso(eval(cons(cons(sym('fn'), cons(nil, cons(num(42))))), {}), num(42))) -- ((fn (a) (cons a 1)) 42) => '(42 . 1) assert(iso(eval(cons(cons(sym('fn'), cons(cons(sym('a')), cons(cons(sym('cons'), cons(sym('a'), cons(num(1))))))), cons(num(42)))), cons(num(42), num(1)))) ================================================ FILE: lisp.tlv ================================================ # .tlv file generated by https://github.com/akkartik/teliva # You may edit it if you are careful; however, you may see cryptic errors if you # violate Teliva's assumptions. # # .tlv files are representations of Teliva programs. Teliva programs consist of # sequences of definitions. Each definition is a table of key/value pairs. Keys # and values are both strings. # # Lines in .tlv files always follow exactly one of the following forms: # - comment lines at the top of the file starting with '#' at column 0 # - beginnings of definitions starting with '- ' at column 0, followed by a # key/value pair # - key/value pairs consisting of ' ' at column 0, containing either a # spaceless value on the same line, or a multi-line value # - multiline values indented by more than 2 spaces, starting with a '>' # # If these constraints are violated, Teliva may unceremoniously crash. Please # report bugs at http://akkartik.name/contact - __teliva_timestamp: original str_helpers: >-- some string helpers from http://lua-users.org/wiki/StringIndexing > >-- index characters using [] >getmetatable('').__index = function(str,i) > if type(i) == 'number' then > return str:sub(i,i) > else > return string[i] > end >end > >-- ranges using (), selected bytes using {} >getmetatable('').__call = function(str,i,j) > if type(i)~='table' then > return str:sub(i,j) > else > local t={} > for k,v in ipairs(i) do > t[k]=str:sub(v,v) > end > return table.concat(t) > end >end > >-- iterate over an ordered sequence >function q(x) > if type(x) == 'string' then > return x:gmatch('.') > else > return ipairs(x) > end >end > >-- insert within string >function string.insert(str1, str2, pos) > return str1:sub(1,pos)..str2..str1:sub(pos+1) >end > >function string.remove(s, pos) > return s:sub(1,pos-1)..s:sub(pos+1) >end > >function string.pos(s, sub) > return string.find(s, sub, 1, true) -- plain=true to disable regular expressions >end > >-- TODO: backport utf-8 support from Lua 5.3 - __teliva_timestamp: original check: >function check(x, msg) > if x then > Window:addch('.') > else > print('F - '..msg) > print(' '..str(x)..' is false/nil') > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original check_eq: >function check_eq(x, expected, msg) > if eq(x, expected) then > Window:addch('.') > else > print('F - '..msg) > print(' expected '..str(expected)..' but got '..str(x)) > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original eq: >function eq(a, b) > if type(a) ~= type(b) then return false end > if type(a) == 'table' then > if #a ~= #b then return false end > for k, v in pairs(a) do > if b[k] ~= v then > return false > end > end > for k, v in pairs(b) do > if a[k] ~= v then > return false > end > end > return true > end > return a == b >end - __teliva_timestamp: original str: >-- smarter tostring >-- slow; used only for debugging >function str(x) > if type(x) == 'table' then > local result = '' > result = result..#x..'{' > for k, v in pairs(x) do > result = result..str(k)..'='..str(v)..', ' > end > result = result..'}' > return result > elseif type(x) == 'string' then > return '"'..x..'"' > end > return tostring(x) >end - __teliva_timestamp: original menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = {} - __teliva_timestamp: original Window: >Window = curses.stdscr() - __teliva_timestamp: original render: >function render(window) > window:clear() > -- draw stuff to screen here > window:attron(curses.A_BOLD) > window:mvaddstr(1, 5, "example app") > window:attrset(curses.A_NORMAL) > for i=0,15 do > window:attrset(curses.color_pair(i)) > window:mvaddstr(3+i, 5, "========================") > end > window:refresh() >end - __teliva_timestamp: original menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = {} - __teliva_timestamp: original update: >function update(window) > local key = window:getch() > -- process key here >end - __teliva_timestamp: original init_colors: >function init_colors() > for i=0,7 do > curses.init_pair(i, i, -1) > end > curses.init_pair(8, 7, 0) > curses.init_pair(9, 7, 1) > curses.init_pair(10, 7, 2) > curses.init_pair(11, 7, 3) > curses.init_pair(12, 7, 4) > curses.init_pair(13, 7, 5) > curses.init_pair(14, 7, 6) > curses.init_pair(15, -1, 15) >end - __teliva_timestamp: original main: >function main() > init_colors() > > while true do > render(Window) > update(Window) > end >end - __teliva_timestamp: original eval: >function eval(x, env) > function symeq(x, s) > return x and x.sym == s > end > if x.sym then > return lookup(env, x.sym) > elseif atom(x) then > return x > -- otherwise x is a pair > elseif symeq(x.car, 'quote') then > return x.cdr > elseif unary_functions[x.car.sym] then > return eval_unary(x, env) > elseif binary_functions[x.car.sym] then > return eval_binary(x, env) > -- special forms that don't always eval all their args > elseif symeq(x.car, 'if') then > return eval_if(x, env) > elseif symeq(x.car.car, 'fn') then > return eval_fn(x, env) > elseif symeq(x.car.car, 'label') then > return eval_label(x, env) > end >end - __teliva_timestamp: original eval_unary: >function eval_unary(x, env) > return unary_functions[x.car.sym](eval(x.cdr.car, env)) >end - __teliva_timestamp: original eval_binary: >function eval_binary(x, env) > return binary_functions[x.car.sym](eval(x.cdr.car, env)) >end - __teliva_timestamp: original unary_functions: >-- format: lisp name = lua function that implements it >unary_functions = { > atom=atom, > car=car, > cdr=cdr, >} - __teliva_timestamp: original binary_functions: >-- format: lisp name = lua function that implements it >binary_functions = { > cons=cons, > iso=iso, >} - __teliva_timestamp: original lookup: >function lookup(env, s) > if env[s] then return env[s] end > if env.next then return lookup(env.next, s) end >end - __teliva_timestamp: original eval_if: >function eval_if(x, env) > -- syntax: (if check b1 b2) > local check = x.cdr.car > local b1 = x.cdr.cdr.car > local b2 = x.cdr.cdr.cdr.car > if eval(check, env) then > return eval(b1, env) > else > return eval(b2, env) > end >end - __teliva_timestamp: original eval_fn: >function eval_fn(x, env) > -- syntax: ((fn params body*) args*) > local callee = x.car > local args = x.cdr > local params = callee.cdr.car > local body = callee.cdr.cdr > return eval_exprs(body, > bind_env(params, args, env)) >end - __teliva_timestamp: original bind_env: >function bind_env(params, args, env) > if params == nil then return env end > local result = {next=env} > while true do > result[params.car.sym] = eval(args.car, env) > params = params.cdr > args = args.cdr > if params == nil then break end > end > return result >end - __teliva_timestamp: original eval_exprs: >function eval_exprs(xs, env) > local result = nil > while xs do > result = eval(xs.car, env) > xs = xs.cdr > end > return result >end - __teliva_timestamp: original eval_label: >function eval_label(x, env) > -- syntax: ((label f (fn params body*)) args*) > local callee = x.car > local args = x.cdr > local f = callee.cdr.car > local fn = callee.cdr.cdr.car > return eval({car=fn, cdr=args}, > bind_env({f}, {callee}, env)) >end - __teliva_timestamp: original atom: >function atom(x) > return x == nil or x.num or x.char or x.str or x.sym >end - __teliva_timestamp: original car: >function car(x) return x.car end - __teliva_timestamp: original cdr: >function cdr(x) return x.cdr end - __teliva_timestamp: original cons: >function cons(x, y) return {car=x, cdr=y} end - __teliva_timestamp: original doc:main: >John McCarthy's Lisp -- without the metacircularity >If you know Lua, this version might be easier to understand. > >Words highlighted like [[this]] are suggestions for places to jump to using ctrl-g (see the menu below). >You can always jump back here using ctrl-b (for 'big picture'). > >Lisp is a programming language that manipulates objects of a few different types. >There are a few _atomic_ types, and one type that can combine them. >The atomic types are what you would expect: numbers, characters, strings, symbols (variables). You can add others. > >The way to combine them is the [[cons]] table which has just two keys: a [[car]] and a [[cdr]]. Both can hold objects, either atoms or other cons tables. > >We'll now build an interpreter that can run programs constructed out of cons tables. > >One thing we'll need for an interpreter is a symbol table (env) that maps symbols to values (objects). >We'll just use a Lua table for this purpose, but with one tweak: a _next_ pointer that allows us to combine tables together. >See [[lookup]] now to get a sense for how we'll use envs. > >Lisp programs are just cons tables and atoms nested to arbitrary depths, constructing trees. A Lisp interpreter walks the tree of code, >performing computations. Since cons tables can point to other cons tables, the tree-walker interpreter [[eval]] is recursive. >As the interpreter gets complex, we'll extract parts of it into their own helper functions: [[eval_unary]], [[eval_binary]], [[eval_if]], and so on. >The helper functions contain recursive calls to [[eval]], so that [[eval]] becomes indirectly recursive, and [[eval]] together with its helpers >is mutually recursive. I sometimes find it helpful to think of them all as just one big function. > >All these mutually recursive functions take the same arguments: a current expression 'x' and the symbol table 'env'. >But really, most of the interpreter is just walking the tree of expressions. Only two functions care about the internals of 'env': > - [[lookup]] which reads within env as we saw before > - [[bind_env]] which creates a new _scope_ of symbols for each new function call. >More complex Lisps add even more arguments to every. single. helper. Each arg will still only really matter to a couple of functions. >But we still have to pass them around all over the place. > >Hopefully this quick overview will help you get a sense for this codebase. > >Here's a reference list of eval helpers: [[eval_unary]], [[eval_binary]], [[eval_if]], [[eval_fn]], [[eval_exprs]], [[eval_label]] >More complex Lisps with more features will likely add helpers for lumpy bits of the language. >Here's a list of primitives implemented in Lua: [[atom]], [[car]], [[cdr]], [[cons]], [[iso]] (for 'isomorphic'; comparing trees all the way down to the leaves) >Here's a list of _constructors_ for creating objects of different types: [[num]], [[char]], [[str]], [[sym]] (and of course [[cons]]) >I should probably add more primitives for operating on numbers, characters and strings.. - __teliva_timestamp: original iso: >function iso(x, y) > if x == nil then return y == nil end > local done={} > -- watch out for the rare cyclical expression > if done[x] then return done[x] == y end > done[x] = y > if atom(x) then > if not atom(y) then return nil end > for k, v in pairs(x) do > if y[k] ~= v then return nil end > end > return true > end > for k, v in pairs(x) do > if not iso(y[k], v) then return nil end > end > for k, v in pairs(y) do > if not iso(x[k], v) then return nil end > end > return true >end ================================================ FILE: manual_tests ================================================ run a program run a program, edit run a program, edit, make an edit, run | edit takes effect run a program with error | big picture run a program, edit, make an error, run run a program, edit, ^g to a different definition, make an edit, ^e to run again run a program, edit, ^g to a non-existent definition run a program, edit, ^g to a different definition, ^g to a different definition, ^e to run again start -> big picture -> edit -> move cursor -> run -> edit | cursor preserved start -> big picture -> edit A -> move cursor -> big picture -> edit B | cursor initialized start -> big picture -> edit -> move cursor -> run -> exit -> start | big picture (optional) start -> big picture -> edit A -> move cursor -> run -> exit -> start -> ... -> edit B | cursor initialized start -> big picture -> edit A -> move cursor -> run -> exit -> start -> ... -> edit B | big picture syntax highlighting for line comments syntax highlighting for multiline comments start -> big picture -> recent changes -> add note -> save | note visible start -> big picture -> arrow keys* | always exactly one definition highlighted It's very important not to leak space on the Lua stack, particularly proportionate to keypresses. That's a recipe for segfaults. This implies that bouncing around between big picture, editor, recent changes, running app.. shouldn't grow the call stack either. == security/privacy program draws over menu -> getch -> Teliva menu is still visible app tries to read/write sensitive teliva files (teliva_edit_buffer, etc.) -> never allowed TODO should we protect .c sources? TODO protect against DoS attack filling up disk assumptions: listing files in a directory is not worth sandboxing since reading their contents is sandboxed and since UNIX permissions protect system directories rmdir() is not worth sandboxing, since it only succeeds on empty directories no need to sandbox unlink() since it's not exposed ================================================ FILE: sandboxing/README.md ================================================ This directory includes some working notes to audit the entire Teliva codebase for side-effects that should be gated/sandboxed. Founding principle for this approach: Side-effects come from the OS. There can be no effects visible outside a Unix process (regardless of language) if it doesn't invoke any OS syscalls. ## Top down Things to secure: * screen? Keep apps from drawing over standard Teliva UI elements. * Teliva currently doesn't stop apps from overwriting the menu, if they're clever. However, it always redraws its UI elements before accepting any input from the keyboard. * code? There are currently no protections against .tlv files clobbering existing definitions. I'm hoping that disallowing native code keeps this safe. Apps can only affect themselves. * files opened (for read/write) on file system * `io_open` * `io_lines` * destinations opened (for read/write) on network * `inet_tryconnect` // `socket_connect` * `inet_tryaccept` // `socket_accept` It seems more difficult to control what is written to a file or socket once it's opened. For starters let's just focus on the interfaces that convert a string path or url to a file descriptor. Scenarios: * (1) app reads system files * (1) app sends data to a remote server * (1) app should _never_ be allowed to open Teliva's system files: - `teliva_editor_state` - app-specific sandboxing policies * (2) app can read from a remote server but not write (POST) * (1) app permissions are saved across restart * (1) permissions the owner grants to one app are not automatically granted to another * (2) downloading a second app with identical name doesn't receive its predecessors permissions * app gains access to a remote server for a legitimate purpose, reads sensitive data from the local system file for legitimate purpose. Now there's nothing preventing it from exfiltrating the sensitive data to the remote server. - (2) solution: make it obvious in the UI that granting both permissions allows an app to do anything. Educate people to separate apps that read sensitive data from apps that access remote servers. - (2) solution: map phases within an app to distinct permission sets * app A legitimately needs to read sensitive data. It saves a copy to file X. app B seems to legitimately needs to access the network, but also asks to read file X. If the owner forgets who wrote file X and what it contains, sensitive data could be exfiltrated. * (3) app wants access to system() or exec() or popen() Difficulty levels 1. I have some sense of how to enforce this. 2. Seems vaguely doable. 3. Seems unlikely to be doable. UX: * distinguish what Teliva can do, what the app can do, and Teliva's ability to police the app. * easily visualize Teliva's ability to police an app. - maybe show a lock in halves; left half = file system, right half = network. One half unlocked = orange. Both unlocked = red. ## Bottom up * `includes`: all `#include`s throughout the codebase. I assume that C the language itself can't invoke any syscalls without at least triggering warnings from the compiler. ``` cd src grep '#include' * */* > ../sandboxing/includes ``` * `system_includes`: all `#include <...>`s throughout the codebase. I assume side-effects require going outside the codebase. `#include`s could smuggle out of the codebase using relative paths (`../`) but I assume it's easy to protect against this using code review. ``` grep '<' sandboxing/includes > sandboxing/system_includes ``` * `unique_system_includes`: deduped ``` sed 's/.*<\|>.*//g' sandboxing/system_includes |sort |uniq > sandboxing/unique_system_includes ``` ================================================ FILE: sandboxing/includes ================================================ kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include "lua.h" kilo.c:#include "teliva.h" lapi.c:#include lapi.c:#include lapi.c:#include lapi.c:#include lapi.c:#include "lua.h" lapi.c:#include "lapi.h" lapi.c:#include "ldebug.h" lapi.c:#include "ldo.h" lapi.c:#include "lfunc.h" lapi.c:#include "lgc.h" lapi.c:#include "lmem.h" lapi.c:#include "lobject.h" lapi.c:#include "lstate.h" lapi.c:#include "lstring.h" lapi.c:#include "ltable.h" lapi.c:#include "ltm.h" lapi.c:#include "lundump.h" lapi.c:#include "lvm.h" lapi.h:#include "lobject.h" lauxlib.c:#include lauxlib.c:#include lauxlib.c:#include lauxlib.c:#include lauxlib.c:#include lauxlib.c:#include lauxlib.c:#include "lua.h" lauxlib.c:#include "lauxlib.h" lauxlib.h:#include lauxlib.h:#include lauxlib.h:#include "lua.h" lbaselib.c:#include lbaselib.c:#include lbaselib.c:#include lbaselib.c:#include lbaselib.c:#include lbaselib.c:#include "lua.h" lbaselib.c:#include "lauxlib.h" lbaselib.c:#include "lualib.h" lcode.c:#include lcode.c:#include "lua.h" lcode.c:#include "lcode.h" lcode.c:#include "ldebug.h" lcode.c:#include "ldo.h" lcode.c:#include "lgc.h" lcode.c:#include "llex.h" lcode.c:#include "lmem.h" lcode.c:#include "lobject.h" lcode.c:#include "lopcodes.h" lcode.c:#include "lparser.h" lcode.c:#include "ltable.h" lcode.h:#include "llex.h" lcode.h:#include "lobject.h" lcode.h:#include "lopcodes.h" lcode.h:#include "lparser.h" ldblib.c:#include ldblib.c:#include ldblib.c:#include ldblib.c:#include "lua.h" ldblib.c:#include "lauxlib.h" ldblib.c:#include "lualib.h" ldebug.c:#include ldebug.c:#include ldebug.c:#include ldebug.c:#include "lua.h" ldebug.c:#include "lapi.h" ldebug.c:#include "lcode.h" ldebug.c:#include "ldebug.h" ldebug.c:#include "ldo.h" ldebug.c:#include "lfunc.h" ldebug.c:#include "lobject.h" ldebug.c:#include "lopcodes.h" ldebug.c:#include "lstate.h" ldebug.c:#include "lstring.h" ldebug.c:#include "ltable.h" ldebug.c:#include "ltm.h" ldebug.c:#include "lvm.h" ldebug.h:#include "lstate.h" ldo.c:#include ldo.c:#include ldo.c:#include ldo.c:#include ldo.c:#include "lua.h" ldo.c:#include "ldebug.h" ldo.c:#include "ldo.h" ldo.c:#include "lfunc.h" ldo.c:#include "lgc.h" ldo.c:#include "lmem.h" ldo.c:#include "lobject.h" ldo.c:#include "lopcodes.h" ldo.c:#include "lparser.h" ldo.c:#include "lstate.h" ldo.c:#include "lstring.h" ldo.c:#include "ltable.h" ldo.c:#include "ltm.h" ldo.c:#include "lundump.h" ldo.c:#include "lvm.h" ldo.c:#include "lzio.h" ldo.h:#include "lobject.h" ldo.h:#include "lstate.h" ldo.h:#include "lzio.h" ldump.c:#include ldump.c:#include "lua.h" ldump.c:#include "lobject.h" ldump.c:#include "lstate.h" ldump.c:#include "lundump.h" lfunc.c:#include lfunc.c:#include "lua.h" lfunc.c:#include "lfunc.h" lfunc.c:#include "lgc.h" lfunc.c:#include "lmem.h" lfunc.c:#include "lobject.h" lfunc.c:#include "lstate.h" lfunc.h:#include "lobject.h" lgc.c:#include lgc.c:#include "lua.h" lgc.c:#include "ldebug.h" lgc.c:#include "ldo.h" lgc.c:#include "lfunc.h" lgc.c:#include "lgc.h" lgc.c:#include "lmem.h" lgc.c:#include "lobject.h" lgc.c:#include "lstate.h" lgc.c:#include "lstring.h" lgc.c:#include "ltable.h" lgc.c:#include "ltm.h" lgc.h:#include "lobject.h" linit.c:#include "lua.h" linit.c:#include "lualib.h" linit.c:#include "lauxlib.h" liolib.c:#include liolib.c:#include liolib.c:#include liolib.c:#include liolib.c:#include "lua.h" liolib.c:#include "lauxlib.h" liolib.c:#include "lualib.h" llex.c:#include llex.c:#include llex.c:#include llex.c:#include "lua.h" llex.c:#include "ldo.h" llex.c:#include "llex.h" llex.c:#include "lobject.h" llex.c:#include "lparser.h" llex.c:#include "lstate.h" llex.c:#include "lstring.h" llex.c:#include "ltable.h" llex.c:#include "lzio.h" llex.h:#include "lobject.h" llex.h:#include "lzio.h" llimits.h:#include llimits.h:#include llimits.h:#include "lua.h" lmathlib.c:#include lmathlib.c:#include lmathlib.c:#include "lua.h" lmathlib.c:#include "lauxlib.h" lmathlib.c:#include "lualib.h" lmem.c:#include lmem.c:#include "lua.h" lmem.c:#include "ldebug.h" lmem.c:#include "ldo.h" lmem.c:#include "lmem.h" lmem.c:#include "lobject.h" lmem.c:#include "lstate.h" lmem.h:#include lmem.h:#include "llimits.h" lmem.h:#include "lua.h" loadlib.c:#include loadlib.c:#include loadlib.c:#include "lua.h" loadlib.c:#include "lauxlib.h" loadlib.c:#include "lualib.h" loadlib.c:#include loadlib.c:#include loadlib.c:#include lobject.c:#include lobject.c:#include lobject.c:#include lobject.c:#include lobject.c:#include lobject.c:#include "lua.h" lobject.c:#include "ldo.h" lobject.c:#include "lmem.h" lobject.c:#include "lobject.h" lobject.c:#include "lstate.h" lobject.c:#include "lstring.h" lobject.c:#include "lvm.h" lobject.h:#include lobject.h:#include "llimits.h" lobject.h:#include "lua.h" lopcodes.c:#include "lopcodes.h" lopcodes.h:#include "llimits.h" loslib.c:#include loslib.c:#include loslib.c:#include loslib.c:#include loslib.c:#include loslib.c:#include "lua.h" loslib.c:#include "lauxlib.h" loslib.c:#include "lualib.h" lparser.c:#include lparser.c:#include "lua.h" lparser.c:#include "lcode.h" lparser.c:#include "ldebug.h" lparser.c:#include "ldo.h" lparser.c:#include "lfunc.h" lparser.c:#include "llex.h" lparser.c:#include "lmem.h" lparser.c:#include "lobject.h" lparser.c:#include "lopcodes.h" lparser.c:#include "lparser.h" lparser.c:#include "lstate.h" lparser.c:#include "lstring.h" lparser.c:#include "ltable.h" lparser.h:#include "llimits.h" lparser.h:#include "lobject.h" lparser.h:#include "lzio.h" lstate.c:#include lstate.c:#include "lua.h" lstate.c:#include "ldebug.h" lstate.c:#include "ldo.h" lstate.c:#include "lfunc.h" lstate.c:#include "lgc.h" lstate.c:#include "llex.h" lstate.c:#include "lmem.h" lstate.c:#include "lstate.h" lstate.c:#include "lstring.h" lstate.c:#include "ltable.h" lstate.c:#include "ltm.h" lstate.h:#include "lua.h" lstate.h:#include "lobject.h" lstate.h:#include "ltm.h" lstate.h:#include "lzio.h" lstring.c:#include lstring.c:#include "lua.h" lstring.c:#include "lmem.h" lstring.c:#include "lobject.h" lstring.c:#include "lstate.h" lstring.c:#include "lstring.h" lstring.h:#include "lgc.h" lstring.h:#include "lobject.h" lstring.h:#include "lstate.h" lstrlib.c:#include lstrlib.c:#include lstrlib.c:#include lstrlib.c:#include lstrlib.c:#include lstrlib.c:#include "lua.h" lstrlib.c:#include "lauxlib.h" lstrlib.c:#include "lualib.h" ltable.c:#include ltable.c:#include ltable.c:#include "lua.h" ltable.c:#include "ldebug.h" ltable.c:#include "ldo.h" ltable.c:#include "lgc.h" ltable.c:#include "lmem.h" ltable.c:#include "lobject.h" ltable.c:#include "lstate.h" ltable.c:#include "ltable.h" ltable.h:#include "lobject.h" ltablib.c:#include ltablib.c:#include "lua.h" ltablib.c:#include "lauxlib.h" ltablib.c:#include "lualib.h" ltm.c:#include ltm.c:#include "lua.h" ltm.c:#include "lobject.h" ltm.c:#include "lstate.h" ltm.c:#include "lstring.h" ltm.c:#include "ltable.h" ltm.c:#include "ltm.h" ltm.h:#include "lobject.h" lua.c:#include lua.c:#include lua.c:#include lua.c:#include lua.c:#include lua.c:#include lua.c:#include lua.c:#include lua.c:#include lua.c:#include lua.c:#include lua.c:#include "lua.h" lua.c:#include "teliva.h" lua.c:#include "lauxlib.h" lua.c:#include "lualib.h" lua.h:#include lua.h:#include lua.h:#include "luaconf.h" lua.h:#include LUA_USER_H luaconf.h:#include luaconf.h:#include luaconf.h:#include luaconf.h:#include luaconf.h:#include luaconf.h:#include lualib.h:#include "lua.h" lundump.c:#include lundump.c:#include "lua.h" lundump.c:#include "ldebug.h" lundump.c:#include "ldo.h" lundump.c:#include "lfunc.h" lundump.c:#include "lmem.h" lundump.c:#include "lobject.h" lundump.c:#include "lstring.h" lundump.c:#include "lundump.h" lundump.c:#include "lzio.h" lundump.h:#include "lobject.h" lundump.h:#include "lzio.h" lvm.c:#include lvm.c:#include lvm.c:#include lvm.c:#include "lua.h" lvm.c:#include "ldebug.h" lvm.c:#include "ldo.h" lvm.c:#include "lfunc.h" lvm.c:#include "lgc.h" lvm.c:#include "lobject.h" lvm.c:#include "lopcodes.h" lvm.c:#include "lstate.h" lvm.c:#include "lstring.h" lvm.c:#include "ltable.h" lvm.c:#include "ltm.h" lvm.c:#include "lvm.h" lvm.h:#include "ldo.h" lvm.h:#include "lobject.h" lvm.h:#include "ltm.h" lzio.c:#include lzio.c:#include "lua.h" lzio.c:#include "llimits.h" lzio.c:#include "lmem.h" lzio.c:#include "lstate.h" lzio.c:#include "lzio.h" lzio.h:#include "lua.h" lzio.h:#include "lmem.h" menu.c:#include menu.c:#include menu.c:#include "lua.h" menu.c:#include "lauxlib.h" menu.c:#include "teliva.h" tlv.c:#include tlv.c:#include tlv.c:#include tlv.c:#include tlv.c:#include tlv.c:#include tlv.c:#include "lua.h" tlv.c:#include "lauxlib.h" lcurses/_helpers.c:#include lcurses/_helpers.c:#include lcurses/_helpers.c:#include lcurses/_helpers.c:#include lcurses/_helpers.c:#include lcurses/_helpers.c:#include lcurses/_helpers.c:#include lcurses/_helpers.c:#include lcurses/_helpers.c:#include lcurses/_helpers.c:#include "../lua.h" lcurses/_helpers.c:#include "../lualib.h" lcurses/_helpers.c:#include "../lauxlib.h" lcurses/chstr.c:#include "_helpers.c" lcurses/compat-5.2.c:#include lcurses/compat-5.2.c:#include lcurses/compat-5.2.c:#include "../lua.h" lcurses/compat-5.2.c:#include "../lauxlib.h" lcurses/compat-5.2.c:#include "compat-5.2.h" lcurses/compat-5.2.c:#include lcurses/compat-5.2.c:#include lcurses/compat-5.2.h:#include lcurses/compat-5.2.h:#include lcurses/compat-5.2.h:#include lcurses/compat-5.2.h:#include "../lua.h" lcurses/compat-5.2.h:#include "../lauxlib.h" lcurses/compat-5.2.h:#include "../lualib.h" lcurses/compat-5.2.h:#include lcurses/curses.c:#include "_helpers.c" lcurses/curses.c:#include "strlcpy.c" lcurses/curses.c:#include "chstr.c" lcurses/curses.c:#include "window.c" lcurses/strlcpy.c:#include lcurses/strlcpy.c:#include lcurses/window.c:#include "../teliva.h" lcurses/window.c:#include "_helpers.c" lcurses/window.c:#include "chstr.c" luasec/compat.h:#include luasec/config.c:#include "compat.h" luasec/config.c:#include "options.h" luasec/config.c:#include "ec.h" luasec/context.c:#include luasec/context.c:#include luasec/context.c:#include luasec/context.c:#include luasec/context.c:#include luasec/context.c:#include luasec/context.c:#include luasec/context.c:#include "../lua.h" luasec/context.c:#include "../lauxlib.h" luasec/context.c:#include "compat.h" luasec/context.c:#include "context.h" luasec/context.c:#include "options.h" luasec/context.c:#include luasec/context.c:#include "ec.h" luasec/context.h:#include "../lua.h" luasec/context.h:#include luasec/context.h:#include "compat.h" luasec/ec.c:#include luasec/ec.c:#include "ec.h" luasec/ec.h:#include "../lua.h" luasec/ec.h:#include luasec/options.c:#include luasec/options.c:#include "options.h" luasec/options.h:#include "compat.h" luasec/options.lua:#include luasec/options.lua:#include "options.h" luasec/ssl.c:#include luasec/ssl.c:#include luasec/ssl.c:#include luasec/ssl.c:#include luasec/ssl.c:#include luasec/ssl.c:#include luasec/ssl.c:#include luasec/ssl.c:#include luasec/ssl.c:#include "../lua.h" luasec/ssl.c:#include "../lauxlib.h" luasec/ssl.c:#include "../luasocket/io.h" luasec/ssl.c:#include "../luasocket/buffer.h" luasec/ssl.c:#include "../luasocket/timeout.h" luasec/ssl.c:#include "../luasocket/socket.h" luasec/ssl.c:#include "x509.h" luasec/ssl.c:#include "context.h" luasec/ssl.c:#include "ssl.h" luasec/ssl.h:#include luasec/ssl.h:#include "../lua.h" luasec/ssl.h:#include "../luasocket/io.h" luasec/ssl.h:#include "../luasocket/buffer.h" luasec/ssl.h:#include "../luasocket/timeout.h" luasec/ssl.h:#include "../luasocket/socket.h" luasec/ssl.h:#include "compat.h" luasec/ssl.h:#include "context.h" luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include "../lua.h" luasec/x509.c:#include "../lauxlib.h" luasec/x509.c:#include "x509.h" luasec/x509.h:#include luasec/x509.h:#include "../lua.h" luasec/x509.h:#include "compat.h" luasocket/auxiliar.c:#include "luasocket.h" luasocket/auxiliar.c:#include "auxiliar.h" luasocket/auxiliar.c:#include luasocket/auxiliar.c:#include luasocket/auxiliar.h:#include "luasocket.h" luasocket/buffer.c:#include "luasocket.h" luasocket/buffer.c:#include "buffer.h" luasocket/buffer.h:#include "luasocket.h" luasocket/buffer.h:#include "io.h" luasocket/buffer.h:#include "timeout.h" luasocket/compat.c:#include "luasocket.h" luasocket/compat.c:#include "compat.h" luasocket/except.c:#include "luasocket.h" luasocket/except.c:#include "except.h" luasocket/except.c:#include luasocket/except.h:#include "luasocket.h" luasocket/inet.c:#include "luasocket.h" luasocket/inet.c:#include "inet.h" luasocket/inet.c:#include luasocket/inet.c:#include luasocket/inet.c:#include luasocket/inet.h:#include "luasocket.h" luasocket/inet.h:#include "socket.h" luasocket/inet.h:#include "timeout.h" luasocket/io.c:#include "luasocket.h" luasocket/io.c:#include "io.h" luasocket/io.h:#include "luasocket.h" luasocket/io.h:#include "timeout.h" luasocket/luasocket.c:#include "luasocket.h" luasocket/luasocket.c:#include "auxiliar.h" luasocket/luasocket.c:#include "except.h" luasocket/luasocket.c:#include "timeout.h" luasocket/luasocket.c:#include "buffer.h" luasocket/luasocket.c:#include "inet.h" luasocket/luasocket.c:#include "tcp.h" luasocket/luasocket.c:#include "udp.h" luasocket/luasocket.c:#include "select.h" luasocket/luasocket.h:#include "../lua.h" luasocket/luasocket.h:#include "../lauxlib.h" luasocket/luasocket.h:#include "compat.h" luasocket/mime.c:#include "luasocket.h" luasocket/mime.c:#include "mime.h" luasocket/mime.c:#include luasocket/mime.c:#include luasocket/mime.h:#include "luasocket.h" luasocket/options.c:#include "luasocket.h" luasocket/options.c:#include "auxiliar.h" luasocket/options.c:#include "options.h" luasocket/options.c:#include "inet.h" luasocket/options.c:#include luasocket/options.h:#include "luasocket.h" luasocket/options.h:#include "socket.h" luasocket/select.c:#include "luasocket.h" luasocket/select.c:#include "socket.h" luasocket/select.c:#include "timeout.h" luasocket/select.c:#include "select.h" luasocket/select.c:#include luasocket/serial.c:#include "luasocket.h" luasocket/serial.c:#include "auxiliar.h" luasocket/serial.c:#include "socket.h" luasocket/serial.c:#include "options.h" luasocket/serial.c:#include "unix.h" luasocket/serial.c:#include luasocket/serial.c:#include luasocket/socket.h:#include "io.h" luasocket/socket.h:#include "wsocket.h" luasocket/socket.h:#include "usocket.h" luasocket/socket.h:#include "timeout.h" luasocket/tcp.c:#include "luasocket.h" luasocket/tcp.c:#include "auxiliar.h" luasocket/tcp.c:#include "socket.h" luasocket/tcp.c:#include "inet.h" luasocket/tcp.c:#include "options.h" luasocket/tcp.c:#include "tcp.h" luasocket/tcp.c:#include luasocket/tcp.h:#include "luasocket.h" luasocket/tcp.h:#include "buffer.h" luasocket/tcp.h:#include "timeout.h" luasocket/tcp.h:#include "socket.h" luasocket/timeout.c:#include "luasocket.h" luasocket/timeout.c:#include "auxiliar.h" luasocket/timeout.c:#include "timeout.h" luasocket/timeout.c:#include luasocket/timeout.c:#include luasocket/timeout.c:#include luasocket/timeout.c:#include luasocket/timeout.c:#include luasocket/timeout.c:#include luasocket/timeout.h:#include "luasocket.h" luasocket/udp.c:#include "luasocket.h" luasocket/udp.c:#include "auxiliar.h" luasocket/udp.c:#include "socket.h" luasocket/udp.c:#include "inet.h" luasocket/udp.c:#include "options.h" luasocket/udp.c:#include "udp.h" luasocket/udp.c:#include luasocket/udp.c:#include luasocket/udp.h:#include "luasocket.h" luasocket/udp.h:#include "timeout.h" luasocket/udp.h:#include "socket.h" luasocket/unix.c:#include "luasocket.h" luasocket/unix.c:#include "unixstream.h" luasocket/unix.c:#include "unixdgram.h" luasocket/unix.h:#include "luasocket.h" luasocket/unix.h:#include "buffer.h" luasocket/unix.h:#include "timeout.h" luasocket/unix.h:#include "socket.h" luasocket/unixdgram.c:#include "luasocket.h" luasocket/unixdgram.c:#include "auxiliar.h" luasocket/unixdgram.c:#include "socket.h" luasocket/unixdgram.c:#include "options.h" luasocket/unixdgram.c:#include "unix.h" luasocket/unixdgram.c:#include luasocket/unixdgram.c:#include luasocket/unixdgram.c:#include luasocket/unixdgram.h:#include "unix.h" luasocket/unixstream.c:#include "luasocket.h" luasocket/unixstream.c:#include "auxiliar.h" luasocket/unixstream.c:#include "socket.h" luasocket/unixstream.c:#include "options.h" luasocket/unixstream.c:#include "unixstream.h" luasocket/unixstream.c:#include luasocket/unixstream.c:#include luasocket/unixstream.h:#include "unix.h" luasocket/usocket.c:#include "luasocket.h" luasocket/usocket.c:#include "socket.h" luasocket/usocket.c:#include "pierror.h" luasocket/usocket.c:#include luasocket/usocket.c:#include luasocket/usocket.c:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/wsocket.c:#include "luasocket.h" luasocket/wsocket.c:#include luasocket/wsocket.c:#include "socket.h" luasocket/wsocket.c:#include "pierror.h" luasocket/wsocket.h:#include luasocket/wsocket.h:#include ================================================ FILE: sandboxing/system_includes ================================================ kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include kilo.c:#include lapi.c:#include lapi.c:#include lapi.c:#include lapi.c:#include lauxlib.c:#include lauxlib.c:#include lauxlib.c:#include lauxlib.c:#include lauxlib.c:#include lauxlib.c:#include lauxlib.h:#include lauxlib.h:#include lbaselib.c:#include lbaselib.c:#include lbaselib.c:#include lbaselib.c:#include lbaselib.c:#include lcode.c:#include ldblib.c:#include ldblib.c:#include ldblib.c:#include ldebug.c:#include ldebug.c:#include ldebug.c:#include ldo.c:#include ldo.c:#include ldo.c:#include ldo.c:#include ldump.c:#include lfunc.c:#include lgc.c:#include liolib.c:#include liolib.c:#include liolib.c:#include liolib.c:#include llex.c:#include llex.c:#include llex.c:#include llimits.h:#include llimits.h:#include lmathlib.c:#include lmathlib.c:#include lmem.c:#include lmem.h:#include loadlib.c:#include loadlib.c:#include loadlib.c:#include loadlib.c:#include loadlib.c:#include lobject.c:#include lobject.c:#include lobject.c:#include lobject.c:#include lobject.c:#include lobject.h:#include loslib.c:#include loslib.c:#include loslib.c:#include loslib.c:#include loslib.c:#include lparser.c:#include lstate.c:#include lstring.c:#include lstrlib.c:#include lstrlib.c:#include lstrlib.c:#include lstrlib.c:#include lstrlib.c:#include ltable.c:#include ltable.c:#include ltablib.c:#include ltm.c:#include lua.c:#include lua.c:#include lua.c:#include lua.c:#include lua.c:#include lua.c:#include lua.c:#include lua.c:#include lua.c:#include lua.c:#include lua.c:#include lua.h:#include lua.h:#include luaconf.h:#include luaconf.h:#include luaconf.h:#include luaconf.h:#include luaconf.h:#include luaconf.h:#include lundump.c:#include lvm.c:#include lvm.c:#include lvm.c:#include lzio.c:#include menu.c:#include menu.c:#include tlv.c:#include tlv.c:#include tlv.c:#include tlv.c:#include tlv.c:#include tlv.c:#include lcurses/_helpers.c:#include lcurses/_helpers.c:#include lcurses/_helpers.c:#include lcurses/_helpers.c:#include lcurses/_helpers.c:#include lcurses/_helpers.c:#include lcurses/_helpers.c:#include lcurses/_helpers.c:#include lcurses/_helpers.c:#include lcurses/compat-5.2.c:#include lcurses/compat-5.2.c:#include lcurses/compat-5.2.c:#include lcurses/compat-5.2.c:#include lcurses/compat-5.2.h:#include lcurses/compat-5.2.h:#include lcurses/compat-5.2.h:#include lcurses/compat-5.2.h:#include lcurses/strlcpy.c:#include lcurses/strlcpy.c:#include luasec/compat.h:#include luasec/context.c:#include luasec/context.c:#include luasec/context.c:#include luasec/context.c:#include luasec/context.c:#include luasec/context.c:#include luasec/context.c:#include luasec/context.c:#include luasec/context.h:#include luasec/ec.c:#include luasec/ec.h:#include luasec/options.c:#include luasec/options.lua:#include luasec/ssl.c:#include luasec/ssl.c:#include luasec/ssl.c:#include luasec/ssl.c:#include luasec/ssl.c:#include luasec/ssl.c:#include luasec/ssl.c:#include luasec/ssl.c:#include luasec/ssl.h:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.c:#include luasec/x509.h:#include luasocket/auxiliar.c:#include luasocket/auxiliar.c:#include luasocket/except.c:#include luasocket/inet.c:#include luasocket/inet.c:#include luasocket/inet.c:#include luasocket/mime.c:#include luasocket/mime.c:#include luasocket/options.c:#include luasocket/select.c:#include luasocket/serial.c:#include luasocket/serial.c:#include luasocket/tcp.c:#include luasocket/timeout.c:#include luasocket/timeout.c:#include luasocket/timeout.c:#include luasocket/timeout.c:#include luasocket/timeout.c:#include luasocket/timeout.c:#include luasocket/udp.c:#include luasocket/udp.c:#include luasocket/unixdgram.c:#include luasocket/unixdgram.c:#include luasocket/unixdgram.c:#include luasocket/unixstream.c:#include luasocket/unixstream.c:#include luasocket/usocket.c:#include luasocket/usocket.c:#include luasocket/usocket.c:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/usocket.h:#include luasocket/wsocket.c:#include luasocket/wsocket.h:#include luasocket/wsocket.h:#include ================================================ FILE: sandboxing/unique_system_includes ================================================ arpa/inet.h assert.h ctype.h dlfcn.h errno.h fcntl.h float.h grp.h limits.h locale.h mach-o/dyld.h math.h ncurses.h net/if.h netdb.h netinet/in.h netinet/tcp.h openssl/asn1.h openssl/bio.h openssl/bn.h openssl/dh.h openssl/ec.h openssl/err.h openssl/evp.h openssl/objects.h openssl/ssl.h openssl/x509.h openssl/x509_vfy.h openssl/x509v3.h pwd.h setjmp.h signal.h stdarg.h stddef.h stdint.h stdio.h stdlib.h string.h strings.h sys/poll.h sys/socket.h sys/stat.h sys/time.h sys/types.h sys/un.h term.h time.h unistd.h windows.h winsock2.h ws2tcpip.h ================================================ FILE: shell.nix ================================================ { pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-21.11.tar.gz") {} }: pkgs.mkShell { LOCALE_ARCHIVE_2_27 = if (pkgs.glibcLocales != null) then "${pkgs.glibcLocales}/lib/locale/locale-archive" else ""; buildInputs = [ pkgs.glibcLocales pkgs.git pkgs.gnumake pkgs.ncurses pkgs.gcc pkgs.openssl ]; shellHook = '' export LC_ALL=en_US.UTF-8 export GIT_SSL_CAINFO=/etc/ssl/certs/ca-certificates.crt export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt ''; } ================================================ FILE: sieve.tlv ================================================ # .tlv file generated by https://github.com/akkartik/teliva # You may edit it if you are careful; however, you may see cryptic errors if you # violate Teliva's assumptions. # # .tlv files are representations of Teliva programs. Teliva programs consist of # sequences of definitions. Each definition is a table of key/value pairs. Keys # and values are both strings. # # Lines in .tlv files always follow exactly one of the following forms: # - comment lines at the top of the file starting with '#' at column 0 # - beginnings of definitions starting with '- ' at column 0, followed by a # key/value pair # - key/value pairs consisting of ' ' at column 0, containing either a # spaceless value on the same line, or a multi-line value # - multiline values indented by more than 2 spaces, starting with a '>' # # If these constraints are violated, Teliva may unceremoniously crash. Please # report bugs at http://akkartik.name/contact - __teliva_timestamp: original check: >function check(x, msg) > if x then > Window:addch('.') > else > print('F - '..msg) > print(' '..str(x)..' is false/nil') > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original check_eq: >function check_eq(x, expected, msg) > if eq(x, expected) then > Window:addch('.') > else > print('F - '..msg) > print(' expected '..str(expected)..' but got '..str(x)) > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original eq: >function eq(a, b) > if type(a) ~= type(b) then return false end > if type(a) == 'table' then > if #a ~= #b then return false end > for k, v in pairs(a) do > if b[k] ~= v then > return false > end > end > for k, v in pairs(b) do > if a[k] ~= v then > return false > end > end > return true > end > return a == b >end - __teliva_timestamp: original str: >-- smarter tostring >-- slow; used only for debugging >function str(x) > if type(x) == 'table' then > local result = '' > result = result..#x..'{' > for k, v in pairs(x) do > result = result..str(k)..'='..str(v)..', ' > end > result = result..'}' > return result > elseif type(x) == 'string' then > return '"'..x..'"' > end > return tostring(x) >end - __teliva_timestamp: original menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = {} - __teliva_timestamp: original Window: >Window = curses.stdscr() - __teliva_timestamp: original main: >function main() > task.spawn(main_task) > task.scheduler() > Window:refresh() > Window:getch() >end - __teliva_timestamp: original main_task: >function main_task() > Window:clear() > local c = task.Channel:new() > task.spawn(counter, c) > for i=1,10 do > print(c:recv()) > end >end - __teliva_timestamp: >Sat Feb 26 21:50:11 2022 __teliva_note: >a simple counter counter: >function counter(c) > local i = 2 > while true do > c:send(i) > i = i+1 > end >end - __teliva_timestamp: >Sat Feb 26 21:54:53 2022 filter_task: >function filter_task(p, cin, cout) > while true do > local i = cin:recv() > if i%p ~= 0 then > cout:send(i) > end > end >end - __teliva_timestamp: >Sat Feb 26 21:55:46 2022 main_task: >function main_task() > local primes = task.Channel:new() > task.spawn(sieve, primes) > for i=1,10 do > print(primes:recv()) > end >end - __teliva_timestamp: >Sat Feb 26 21:59:37 2022 __teliva_note: >filter out multiples of a single number sieve: >function sieve(ch) > local iota = task.Channel:new() > task.spawn(counter, iota) > task.spawn(filter_task, 2, iota, ch) >end - __teliva_timestamp: >Sat Feb 26 22:08:07 2022 __teliva_note: >implement the complete sieve algorithm sieve: >-- Set up a Sieve of Eratosthenes (https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) >-- for computing prime numbers by chaining tasks, one per prime. >-- Each task is responsible for filtering out all multiples of its prime. >function sieve(primes_ch) > local c = task.Channel:new() > task.spawn(counter, c) > while true do > local p, newc = c:recv(), task.Channel:new() > primes_ch:send(p) > task.spawn(filter_task, p, c, newc) > c = newc > end >end - __teliva_timestamp: >Sat Feb 26 22:09:47 2022 __teliva_note: >infinite primes main_task: >function main_task() > local primes = task.Channel:new() > task.spawn(sieve, primes) > while true do > Window:addstr(primes:recv()) > Window:addstr(' ') > Window:refresh() > end >end - __teliva_timestamp: >Sat Feb 26 22:09:47 2022 __teliva_note: >clear screen when it fills up; pause on keypress > >In Teliva getch() implicitly refreshes the screen. main_task: >function main_task() > Window:nodelay(true) > Window:clear() > local primes = task.Channel:new() > task.spawn(sieve, primes) > local h, w = Window:getmaxyx() > while true do > Window:addstr(primes:recv()) > Window:addstr(' ') > local c = Window:getch() > if c then break end -- key pressed > local y, x = Window:getyx() > if y > h-1 then > Window:clear() > end > end > print('key pressed; done') > Window:nodelay(false) >end - __teliva_timestamp: >Sat Feb 26 22:27:25 2022 doc:blurb: >Sieve of Eratosthenes >https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes > >A demonstration of tasks and channels, the primitives for (cooperative) concurrency in Teliva. > >We string together a cascade of tasks connected by channels. Every prime number gets a new task that prints the first incoming number, and then filters out multiples of it from the incoming channel. > >This approach has the advantage that we don't need to create an array of n numbers to compute primes less than n. > >However, we still need to create p tasks and p channels if there are p primes less than n. Probably not worth it, given tasks and channels are much larger than numbers. This is just a demo. > >The noticeable periodic pauses are perhaps due to garbage collection. ================================================ FILE: smol.tlv ================================================ # .tlv file generated by https://github.com/akkartik/teliva # You may edit it if you are careful; however, you may see cryptic errors if you # violate Teliva's assumptions. # # .tlv files are representations of Teliva programs. Teliva programs consist of # sequences of definitions. Each definition is a table of key/value pairs. Keys # and values are both strings. # # Lines in .tlv files always follow exactly one of the following forms: # - comment lines at the top of the file starting with '#' at column 0 # - beginnings of definitions starting with '- ' at column 0, followed by a # key/value pair # - key/value pairs consisting of ' ' at column 0, containing either a # spaceless value on the same line, or a multi-line value # - multiline values indented by more than 2 spaces, starting with a '>' # # If these constraints are violated, Teliva may unceremoniously crash. Please # report bugs at http://akkartik.name/contact - __teliva_timestamp: original str_helpers: >-- some string helpers from http://lua-users.org/wiki/StringIndexing > >-- index characters using [] >getmetatable('').__index = function(str,i) > if type(i) == 'number' then > return str:sub(i,i) > else > return string[i] > end >end > >-- ranges using (), selected bytes using {} >getmetatable('').__call = function(str,i,j) > if type(i)~='table' then > return str:sub(i,j) > else > local t={} > for k,v in ipairs(i) do > t[k]=str:sub(v,v) > end > return table.concat(t) > end >end > >-- iterate over an ordered sequence >function q(x) > if type(x) == 'string' then > return x:gmatch('.') > else > return ipairs(x) > end >end > >-- insert within string >function string.insert(str1, str2, pos) > return str1:sub(1,pos)..str2..str1:sub(pos+1) >end > >function string.remove(s, pos) > return s:sub(1,pos-1)..s:sub(pos+1) >end > >function string.pos(s, sub) > return string.find(s, sub, 1, true) -- plain=true to disable regular expressions >end > >-- TODO: backport utf-8 support from Lua 5.3 - __teliva_timestamp: original debugy: >debugy = 5 - __teliva_timestamp: original dbg: >-- helper for debug by print; overlay debug information towards the right >-- reset debugy every time you refresh screen >function dbg(window, s) > local oldy = 0 > local oldx = 0 > oldy, oldx = window:getyx() > window:mvaddstr(debugy, 60, s) > debugy = debugy+1 > window:mvaddstr(oldy, oldx, '') >end - __teliva_timestamp: original check: >function check(x, msg) > if x then > Window:addch('.') > else > print('F - '..msg) > print(' '..str(x)..' is false/nil') > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original check_eq: >function check_eq(x, expected, msg) > if eq(x, expected) then > Window:addch('.') > else > print('F - '..msg) > print(' expected '..str(expected)..' but got '..str(x)) > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original eq: >function eq(a, b) > if type(a) ~= type(b) then return false end > if type(a) == 'table' then > if #a ~= #b then return false end > for k, v in pairs(a) do > if b[k] ~= v then > return false > end > end > for k, v in pairs(b) do > if a[k] ~= v then > return false > end > end > return true > end > return a == b >end - __teliva_timestamp: original str: >-- smarter tostring >-- slow; used only for debugging >function str(x) > if type(x) == 'table' then > local result = '' > result = result..#x..'{' > for k, v in pairs(x) do > result = result..str(k)..'='..str(v)..', ' > end > result = result..'}' > return result > elseif type(x) == 'string' then > return '"'..x..'"' > end > return tostring(x) >end - __teliva_timestamp: original find_index: >function find_index(arr, x) > for n, y in ipairs(arr) do > if x == y then > return n > end > end >end - __teliva_timestamp: original trim: >function trim(s) > return s:gsub('^%s*', ''):gsub('%s*$', '') >end - __teliva_timestamp: original split: >function split(s, d) > result = {} > for match in (s..d):gmatch("(.-)"..d) do > table.insert(result, match); > end > return result >end - __teliva_timestamp: original map: >-- only for arrays >function map(l, f) > result = {} > for _, x in ipairs(l) do > table.insert(result, f(x)) > end > return result >end - __teliva_timestamp: original reduce: >-- only for arrays >function reduce(l, f, init) > result = init > for _, x in ipairs(l) do > result = f(result, x) > end > return result >end - __teliva_timestamp: original filter: >function filter(h, f) > result = {} > for k, v in pairs(h) do > if f(k, v) then > result[k] = v > end > end > return result >end - __teliva_timestamp: original ifilter: >-- only for arrays >function ifilter(l, f) > result = {} > for _, x in ipairs(l) do > if f(x) then > table.insert(result, x) > end > end > return result >end - __teliva_timestamp: original sort_letters: >function sort_letters(s) > tmp = {} > for i=1,#s do > table.insert(tmp, s[i]) > end > table.sort(tmp) > local result = '' > for _, c in pairs(tmp) do > result = result..c > end > return result >end > >function test_sort_letters(s) > check_eq(sort_letters(''), '', 'test_sort_letters: empty') > check_eq(sort_letters('ba'), 'ab', 'test_sort_letters: non-empty') > check_eq(sort_letters('abba'), 'aabb', 'test_sort_letters: duplicates') >end - __teliva_timestamp: original count_letters: >-- TODO: handle unicode >function count_letters(s) > local result = {} > for i=1,s:len() do > local c = s[i] > if result[c] == nil then > result[c] = 1 > else > result[c] = result[c] + 1 > end > end > return result >end - __teliva_timestamp: original count: >-- turn an array of elements into a map from elements to their frequency >-- analogous to count_letters for non-strings >function count(a) > local result = {} > for i, v in ipairs(a) do > if result[v] == nil then > result[v] = 1 > else > result[v] = result[v] + 1 > end > end > return result >end - __teliva_timestamp: original union: >function union(a, b) > for k, v in pairs(b) do > a[k] = v > end > return a >end - __teliva_timestamp: original subtract: >-- set subtraction >function subtract(a, b) > for k, v in pairs(b) do > a[k] = nil > end > return a >end - __teliva_timestamp: original all: >-- universal quantifier on sets >function all(s, f) > for k, v in pairs(s) do > if not f(k, v) then > return false > end > end > return true >end - __teliva_timestamp: original to_array: >-- turn a set into an array >-- drops values >function to_array(h) > local result = {} > for k, _ in pairs(h) do > table.insert(result, k) > end > return result >end - __teliva_timestamp: original append: >-- concatenate list 'elems' into 'l', modifying 'l' in the process >function append(l, elems) > for i=1,#elems do > table.insert(l, elems[i]) > end >end - __teliva_timestamp: original prepend: >-- concatenate list 'elems' into the start of 'l', modifying 'l' in the process >function prepend(l, elems) > for i=1,#elems do > table.insert(l, i, elems[i]) > end >end - __teliva_timestamp: original all_but: >function all_but(x, idx) > if type(x) == 'table' then > local result = {} > for i, elem in ipairs(x) do > if i ~= idx then > table.insert(result,elem) > end > end > return result > elseif type(x) == 'string' then > if idx < 1 then return x:sub(1) end > return x:sub(1, idx-1) .. x:sub(idx+1) > else > error('all_but: unsupported type '..type(x)) > end >end > >function test_all_but() > check_eq(all_but('', 0), '', 'all_but: empty') > check_eq(all_but('abc', 0), 'abc', 'all_but: invalid low index') > check_eq(all_but('abc', 4), 'abc', 'all_but: invalid high index') > check_eq(all_but('abc', 1), 'bc', 'all_but: first index') > check_eq(all_but('abc', 3), 'ab', 'all_but: final index') > check_eq(all_but('abc', 2), 'ac', 'all_but: middle index') >end - __teliva_timestamp: original set: >function set(l) > local result = {} > for i, elem in ipairs(l) do > result[elem] = true > end > return result >end - __teliva_timestamp: original set_eq: >function set_eq(l1, l2) > return eq(set(l1), set(l2)) >end > >function test_set_eq() > check(set_eq({1}, {1}), 'set_eq: identical') > check(not set_eq({1, 2}, {1, 3}), 'set_eq: different') > check(set_eq({1, 2}, {2, 1}), 'set_eq: order') > check(set_eq({1, 2, 2}, {2, 1}), 'set_eq: duplicates') >end - __teliva_timestamp: original clear: >function clear(lines) > while #lines > 0 do > table.remove(lines) > end >end - __teliva_timestamp: original zap: >function zap(target, src) > clear(target) > append(target, src) >end - __teliva_timestamp: original mfactorial: >-- memoized version of factorial >-- doesn't memoize recursive calls, but may be good enough >mfactorial = memo1(factorial) - __teliva_timestamp: original factorial: >function factorial(n) > local result = 1 > for i=1,n do > result = result*i > end > return result >end - __teliva_timestamp: original memo1: >-- a higher-order function that takes a function of a single arg >-- (that never returns nil) >-- and returns a memoized version of it >function memo1(f) > local memo = {} > return function(x) > if memo[x] == nil then > memo[x] = f(x) > end > return memo[x] > end >end > >-- mfactorial doesn't seem noticeably faster >function test_memo1() > for i=0,30 do > check_eq(mfactorial(i), factorial(i), 'memo1 over factorial: '..str(i)) > end >end - __teliva_timestamp: original num_permutations: >-- number of permutations of n distinct objects, taken r at a time >function num_permutations(n, r) > return factorial(n)/factorial(n-r) >end > >-- mfactorial doesn't seem noticeably faster >function test_memo1() > for i=0,30 do > for j=0,i do > check_eq(num_permutations(i, j), mfactorial(i)/mfactorial(i-j), 'num_permutations memoizes: '..str(i)..'P'..str(j)) > end > end >end - __teliva_timestamp: original menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = {} - __teliva_timestamp: original Window: >Window = curses.stdscr() - __teliva_timestamp: original window: >-- constructor for fake screen and window >-- call it like this: >-- local w = window{ >-- kbd=kbd('abc'), >-- scr=scr{h=5, w=4}, >-- } >-- eventually it'll do everything a real ncurses window can >function window(h) > h.__index = h > setmetatable(h, h) > h.__index = function(table, key) > return rawget(h, key) > end > h.attrset = function(self, x) > self.scr.attrs = x > end > h.attron = function(self, x) > -- currently same as attrset since Lua 5.1 doesn't have bitwise operators > -- doesn't support multiple attrs at once >-- local old = self.scr.attrs >-- self.scr.attrs = old|x > self.scr.attrs = x > end > h.attroff = function(self, x) > -- currently borked since Lua 5.1 doesn't have bitwise operators > -- doesn't support multiple attrs at once >-- local old = self.scr.attrs >-- self.scr.attrs = old & (~x) > self.scr.attrs = curses.A_NORMAL > end > h.getch = function(self) > local c = table.remove(h.kbd, 1) > if c == nil then return c end > return string.byte(c) -- for verisimilitude with ncurses > end > h.addch = function(self, c) > local scr = self.scr > if c == '\n' then > scr.cursy = scr.cursy+1 > scr.cursx = 0 > return > end > if scr.cursy <= scr.h then > scr[scr.cursy][scr.cursx] = {data=c, attrs=scr.attrs} > scr.cursx = scr.cursx+1 > if scr.cursx > scr.w then > scr.cursy = scr.cursy+1 > scr.cursx = 1 > end > end > end > h.addstr = function(self, s) > for i=1,s:len() do > self:addch(s[i]) > end > end > h.mvaddch = function(self, y, x, c) > self.scr.cursy = y > self.scr.cursx = x > self:addch(c) > end > h.mvaddstr = function(self, y, x, s) > self.scr.cursy = y > self.scr.cursx = x > self:addstr(s) > end > h.clear = function(self) > clear_scr(self.scr) > end > h.refresh = function(self) > -- nothing > end > return h >end - __teliva_timestamp: original kbd: >function kbd(keys) > local result = {} > for i=1,keys:len() do > table.insert(result, keys[i]) > end > return result >end - __teliva_timestamp: original scr: >function scr(props) > props.cursx = 1 > props.cursy = 1 > clear_scr(props) > return props >end - __teliva_timestamp: original clear_scr: >function clear_scr(props) > props.cursy = 1 > props.cursx = 1 > for y=1,props.h do > props[y] = {} > for x=1,props.w do > props[y][x] = {data=' ', attrs=curses.A_NORMAL} > end > end > return props >end - __teliva_timestamp: original check_screen: >function check_screen(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > check_eq(window.scr[y][x].data, contents[i], message..'/'..y..','..x) > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end > >-- putting it all together, an example test of both keyboard and screen >function test_check_screen() > local lines = { > c='123', > d='234', > a='345', > b='456', > } > local w = window{ > kbd=kbd('abc'), > scr=scr{h=3, w=5}, > } > local y = 1 > while true do > local b = w:getch() > if b == nil then break end > w:mvaddstr(y, 1, lines[string.char(b)]) > y = y+1 > end > check_screen(w, '345 '.. > '456 '.. > '123 ', > 'test_check_screen') >end - __teliva_timestamp: original check_reverse: >function check_reverse(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > if contents[i] ~= ' ' then > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & curses.A_REVERSE, message..'/'..y..','..x) > check_eq(window.scr[y][x].attrs, curses.A_REVERSE, message..'/'..y..','..x) > else > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & (~curses.A_REVERSE), message..'/'..y..','..x) > check(window.scr[y][x].attrs ~= curses.A_REVERSE, message..'/'..y..','..x) > end > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end - __teliva_timestamp: original check_bold: >function check_bold(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > if contents[i] ~= ' ' then > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & curses.A_BOLD, message..'/'..y..','..x) > check_eq(window.scr[y][x].attrs, curses.A_BOLD, message..'/'..y..','..x) > else > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & (~curses.A_BOLD), message..'/'..y..','..x) > check(window.scr[y][x].attrs ~= curses.A_BOLD, message..'/'..y..','..x) > end > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end - __teliva_timestamp: original check_color: >-- check which parts of a screen have the given color_pair >function check_color(window, cp, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > if contents[i] ~= ' ' then > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & curses.color_pair(cp), message..'/'..y..','..x) > check_eq(window.scr[y][x].attrs, curses.color_pair(cp), message..'/'..y..','..x) > else > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & (~curses.A_BOLD), message..'/'..y..','..x) > check(window.scr[y][x].attrs ~= curses.color_pair(cp), message..'/'..y..','..x) > end > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end - __teliva_timestamp: original sep: >-- horizontal separator >function sep(window) > local y, _ = window:getyx() > window:mvaddstr(y+1, 0, '') > local _, cols = window:getmaxyx() > for col=1,cols do > window:addstr('_') > end >end - __teliva_timestamp: original render: >function render(window) > window:clear() > -- draw stuff to screen here > window:attron(curses.A_BOLD) > window:mvaddstr(1, 5, "example app") > window:attrset(curses.A_NORMAL) > for i=0,15 do > window:attrset(curses.color_pair(i)) > window:mvaddstr(3+i, 5, "========================") > end > window:refresh() >end - __teliva_timestamp: original update: >function update(window) > local key = window:getch() > -- process key here >end - __teliva_timestamp: original init_colors: >function init_colors() > for i=0,7 do > curses.init_pair(i, i, -1) > end > curses.init_pair(8, 7, 0) > curses.init_pair(9, 7, 1) > curses.init_pair(10, 7, 2) > curses.init_pair(11, 7, 3) > curses.init_pair(12, 7, 4) > curses.init_pair(13, 7, 5) > curses.init_pair(14, 7, 6) > curses.init_pair(15, -1, 15) >end - __teliva_timestamp: original main: >function main() > init_colors() > > while true do > render(Window) > update(Window) > end >end - __teliva_timestamp: original doc:blurb: >To show a brief description of the app on the 'big picture' screen, put the text in a special buffer called 'doc:blurb'. > >You can also override the default big picture screen entirely by creating a buffer called 'doc:main'. - __teliva_timestamp: >Mon Apr 11 21:47:50 2022 main: >function main() > init_colors() > read_feeds() > > while true do > render(Window) > update(Window) > end >end - __teliva_timestamp: >Mon Apr 11 21:49:18 2022 read_feeds: >function read_feeds() > local f = start_reading('feeds') > while true > local line = f.read() > if line == nil then break end > end >end - __teliva_timestamp: >Mon Apr 11 21:52:26 2022 read_feeds: >function read_feeds() > local f = start_reading('feeds') > Feeds = {} > while true > local feed = f.read() > if feed == nil then break end > table.insert(Feeds, feed) > end >end - __teliva_timestamp: >Mon Apr 11 21:52:33 2022 Feeds: >Feeds = {} - __teliva_timestamp: >Mon Apr 11 21:52:59 2022 render: >function render(window) > window:clear() > -- draw stuff to screen here > for i, feed in ipairs(Feeds) do > print(feed) > end > window:refresh() >end - __teliva_timestamp: >Mon Apr 11 21:53:11 2022 read_feeds: >function read_feeds() > local f = start_reading('feeds') > Feeds = {} > while true do > local feed = f.read() > if feed == nil then break end > table.insert(Feeds, feed) > end >end - __teliva_timestamp: >Mon Apr 11 21:53:22 2022 read_feeds: >function read_feeds() > local f = start_reading(nil, 'feeds') > Feeds = {} > while true do > local feed = f.read() > if feed == nil then break end > table.insert(Feeds, feed) > end >end - __teliva_timestamp: >Mon Apr 11 21:53:39 2022 read_feeds: >function read_feeds() > local f = start_reading(nil, 'feeds') > if f == nil then return end > Feeds = {} > while true do > local feed = f.read() > if feed == nil then break end > table.insert(Feeds, feed) > end >end - __teliva_timestamp: >Fri Apr 22 22:39:15 2022 menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = { > {'^r', 'refresh all feeds'}, >} - __teliva_timestamp: >Fri Apr 22 22:39:45 2022 update: >function update(window) > local key = window:getch() > if key == 18 then -- ctrl-r > print('aaa') > window:getch() > end >end - __teliva_timestamp: >Fri Apr 22 22:40:02 2022 update: >function update(window) > local key = window:getch() > if key == 18 then -- ctrl-r > download_feeds() > end >end - __teliva_timestamp: >Fri Apr 22 22:40:27 2022 update: >function update(window) > local key = window:getch() > if key == 18 then -- ctrl-r > reload_feeds() > end >end - __teliva_timestamp: >Fri Apr 22 22:40:37 2022 menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = { > {'^r', 'reload feeds'}, >} - __teliva_timestamp: >Fri Apr 22 22:41:00 2022 reload_feeds: >function reload_feeds() > print('aaa') > Window:getch() >end - __teliva_timestamp: >Fri Apr 22 22:41:37 2022 reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > print(feed) > end > Window:getch() >end - __teliva_timestamp: >Fri Apr 22 22:43:36 2022 reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > local response, status, headers = http.request(feed) > Window:clear() > print(response) > Window:getch() > end >end - __teliva_timestamp: >Fri Apr 22 22:55:25 2022 Items: >Items = {} - __teliva_timestamp: >Fri Apr 22 22:57:54 2022 reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > local response, status, headers = http.request(feed) > print(type(status)) >--? parse_items(response) > Window:clear() > print(response) >--? print(feed, #Items) > Window:getch() > end >end - __teliva_timestamp: >Fri Apr 22 22:58:15 2022 reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > local response, status, headers = http.request(feed) > Window:clear() > print(type(status)) >--? parse_items(response) > print(response) >--? print(feed, #Items) > Window:getch() > end >end - __teliva_timestamp: >Fri Apr 22 22:59:10 2022 reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > local response, status, headers = http.request(feed) > if response == 200 then > parse_items(response) > Window:clear() > print(response) >--? print(feed, #Items) > Window:getch() > end > end >end - __teliva_timestamp: >Fri Apr 22 22:59:38 2022 reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > local response, status, headers = http.request(feed) > print(status) > if status == 200 then > parse_items(response) > Window:clear() > print(response) >--? print(feed, #Items) > Window:getch() > end > end >end - __teliva_timestamp: >Fri Apr 22 23:01:46 2022 parse_items: >function parse_items(lines) > for line in lines:gmatch('[^\n]*') do > print('^', line, '$') > end >end - __teliva_timestamp: >Fri Apr 22 23:02:26 2022 reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > local response, status, headers = http.request(feed) > print(status) > if status == 200 then > Window:clear() > parse_items(response) > print(response) >--? print(feed, #Items) > Window:getch() > end > end >end - __teliva_timestamp: >Fri Apr 22 23:02:53 2022 parse_items: >function parse_items(lines) > for line in lines:gmatch('[^\n]+') do > print('^', line, '$') > end >end - __teliva_timestamp: >Fri Apr 22 23:06:14 2022 parse_items: >function parse_items(lines) > for line in lines:gmatch('[^\n]+') do > local t, text = line:gmatch('([^%s]+)\t(.*)') > print('^', t, '--', text, '$') > end >end - __teliva_timestamp: >Fri Apr 22 23:06:29 2022 parse_items: >function parse_items(lines) > for line in lines:gmatch('[^\n]+') do > local t, text = line:match('([^%s]+)\t(.*)') > print('^', t, '--', text, '$') > end >end - __teliva_timestamp: >Fri Apr 22 23:07:06 2022 reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > local response, status, headers = http.request(feed) > print(status) > if status == 200 then > Window:clear() > parse_items(feed, response) > print(response) >--? print(feed, #Items) > Window:getch() > end > end >end - __teliva_timestamp: >Fri Apr 22 23:10:44 2022 parse_items: >function parse_items(feed, lines) > if Items[feed] == nil then Items[feed] = {} end > for line in lines:gmatch('[^\n]+') do > local t, text = line:match('([^%s]+)\t(.*)') > table.insert(Items[feed], {time=parse_time(t), text=text}) > end > table.sort(Items[feed], function(a, b) return a.time < b.time end) >end - __teliva_timestamp: >Fri Apr 22 23:11:24 2022 reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > local response, status, headers = http.request(feed) > print(status) > if status == 200 then > Window:clear() > parse_items(feed, response) > for _, x in ipairs(Items) do > print(x.time, x.text) >--? print(feed, #Items) > Window:getch() > end > end >end - __teliva_timestamp: >Fri Apr 22 23:11:36 2022 reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > local response, status, headers = http.request(feed) > print(status) > if status == 200 then > Window:clear() > parse_items(feed, response) > for _, x in ipairs(Items) do > print(x.time, x.text) > end >--? print(feed, #Items) > Window:getch() > end > end >end - __teliva_timestamp: >Fri Apr 22 23:12:05 2022 parse_time: >function parse_time(t) > return t >end - __teliva_timestamp: >Fri Apr 22 23:13:02 2022 reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > local response, status, headers = http.request(feed) > print(status) > if status == 200 then > Window:clear() > parse_items(feed, response) > for _, x in ipairs(Items[feed]) do > print(x.time, x.text) > end >--? print(feed, #Items) > Window:getch() > end > end >end - __teliva_timestamp: >Fri Apr 22 23:13:50 2022 parse_items: >function parse_items(feed, lines) > if Items[feed] == nil then Items[feed] = {} end > for line in lines:gmatch('[^\n]+') do > local t, text = line:match('([^%s]+)\t(.*)') > print(t, text) > table.insert(Items[feed], {time=parse_time(t), text=text}) > end > table.sort(Items[feed], function(a, b) return a.time < b.time end) >end - __teliva_timestamp: >Fri Apr 22 23:14:36 2022 parse_items: >function parse_items(feed, lines) > if Items[feed] == nil then Items[feed] = {} end > for line in lines:gmatch('[^\n]+') do > print(line) > local t, text = line:match('([^%s]+)\t(.*)') > print(t, text) > table.insert(Items[feed], {time=parse_time(t), text=text}) > end > table.sort(Items[feed], function(a, b) return a.time < b.time end) >end - __teliva_timestamp: >Fri Apr 22 23:19:20 2022 parse_items: >function parse_items(feed, lines) > if Items[feed] == nil then Items[feed] = {} end > for line in lines:gmatch('[^\n]+') do > print(line) > local t, status = line:match('([^%s]+)\t(.*)') > if t and status then > print(t, text) > table.insert(Items[feed], {time=parse_time(t), text=text}) > end > end > table.sort(Items[feed], function(a, b) return a.time < b.time end) >end - __teliva_timestamp: >Fri Apr 22 23:19:44 2022 parse_items: >function parse_items(feed, lines) > if Items[feed] == nil then Items[feed] = {} end > for line in lines:gmatch('[^\n]+') do > print(line) > local t, status = line:match('([^%s]+)\t(.*)') > if t and status then > print(t, status) > table.insert(Items[feed], {time=parse_time(t), status=status}) > end > end > table.sort(Items[feed], function(a, b) return a.time < b.time end) >end - __teliva_timestamp: >Fri Apr 22 23:19:56 2022 reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > local response, status, headers = http.request(feed) > print(status) > if status == 200 then > Window:clear() > parse_items(feed, response) > for _, x in ipairs(Items[feed]) do > print(x.time, x.status) > end >--? print(feed, #Items) > Window:getch() > end > end >end - __teliva_timestamp: >Fri Apr 22 23:21:40 2022 parse_items: >function parse_items(feed, lines) > if Items[feed] == nil then Items[feed] = {} end > for line in lines:gmatch('[^\n]+') do > local t, status = line:match('([^%s]+)\t(.*)') > if t and status then > table.insert(Items[feed], {time=parse_time(t), status=status}) > end > end > table.sort(Items[feed], function(a, b) return a.time < b.time end) >end - __teliva_timestamp: >Fri Apr 22 23:23:29 2022 reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > local response, status, headers = http.request(feed) > print(status) > if status == 200 then > Window:clear() > parse_items(feed, response) > local all_items = {} > for feed, items in pairs(Items) do > for _, item in ipairs(items) do > table.insert(all_items, item) > end > end > table.sort(all_items, function(a, b) return a.time < b.time end) > for _, item in ipairs(all_items) do > print(x.time, x.status) > end >--? print(feed, #Items) > Window:getch() > end > end >end - __teliva_timestamp: >Fri Apr 22 23:23:48 2022 reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > local response, status, headers = http.request(feed) > print(status) > if status == 200 then > Window:clear() > parse_items(feed, response) > local all_items = {} > for feed, items in pairs(Items) do > for _, item in ipairs(items) do > table.insert(all_items, item) > end > end > table.sort(all_items, function(a, b) return a.time < b.time end) > for _, x in ipairs(all_items) do > print(x.time, x.status) > end >--? print(feed, #Items) > Window:getch() > end > end >end - __teliva_timestamp: >Fri Apr 22 23:24:28 2022 parse_items: >function parse_items(feed, lines) > if Items[feed] == nil then Items[feed] = {} end > for line in lines:gmatch('[^\n]+') do > local t, status = line:match('([^%s]+)\t(.*)') > if t and status then > table.insert(Items[feed], {feed=feed, time=parse_time(t), status=status}) > end > end > table.sort(Items[feed], function(a, b) return a.time < b.time end) >end - __teliva_timestamp: >Fri Apr 22 23:25:34 2022 reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > local response, status, headers = http.request(feed) > print(status) > if status == 200 then > Window:clear() > parse_items(feed, response) > local all_items = {} > for feed, items in pairs(Items) do > for _, item in ipairs(items) do > table.insert(all_items, item) > end > end > table.sort(all_items, function(a, b) return a.time < b.time end) > for _, x in ipairs(all_items) do > print(x.feed, x.time, x.status) > end >--? print(feed, #Items) > Window:getch() > end > end >end - __teliva_timestamp: >Fri Apr 22 23:27:08 2022 reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > local response, status, headers = http.request(feed) > print(status) > if status == 200 then > Window:clear() > parse_items(feed, response) > local all_items = {} > for feed, items in pairs(Items) do > for _, item in ipairs(items) do > table.insert(all_items, item) > end > end > table.sort(all_items, function(a, b) return a.time < b.time end) > for _, x in ipairs(all_items) do > print(x.feed, x.time, x.status) > end > Window:getch() > end > end >end - __teliva_timestamp: >Fri Apr 22 23:29:49 2022 parse_items: >function parse_items(feed, lines) > if Items[feed] == nil then Items[feed] = {} end > for line in lines:gmatch('[^\n]+') do > local t, status = line:match('([^%s]+)\t(.*)') > if t and status then > table.insert(Items[feed], {feed=feed, time=t, parsed_time=parse_time(t), status=status}) > end > end > table.sort(Items[feed], compare_parsed_time) >end - __teliva_timestamp: >Fri Apr 22 23:31:16 2022 compare_parsed_time: >function compare_parsed_time(a, b) > local a, b = a.parsed_time, b.parsed_time > if a. - __teliva_timestamp: >Fri Apr 22 23:34:46 2022 parse_time: >function parse_time(t) > local year, month, day, hour, min, sec, patt_end = str:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)[Tt](%d%d%.?%d*):(%d%d):(%d%d)()") > return { year=year, month=month, day=day, hour=hour, min=min, sec=sec } >end - __teliva_timestamp: >Fri Apr 22 23:36:13 2022 compare_parsed_time: >function compare_parsed_time(a, b) > local a, b = a.parsed_time, b.parsed_time > if a.year ~= b.year then return a.year < b.year end > if a.month ~= b.month then return a.month < b.month end > if a.day ~= b.day then return a.day < b.day end > if a.hour ~= b.hour then return a.hour < b.hour end > if a.min ~= b.min then return a.min < b.min end > return a.sec < b.sec >end - __teliva_timestamp: >Fri Apr 22 23:36:35 2022 parse_time: >function parse_time(t) > local year, month, day, hour, min, sec, patt_end = t:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)[Tt](%d%d%.?%d*):(%d%d):(%d%d)()") > return { year=year, month=month, day=day, hour=hour, min=min, sec=sec } >end - __teliva_timestamp: >Fri Apr 22 23:39:20 2022 parse_time: >function parse_time(t) > local year, month, day, hour, min, sec, tz_hour, tz_min = t:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)[Tt](%d%d%.?%d*):(%d%d):(%d%d)[+-](%d%d):(%d%d)") > return { year=year, month=month, day=day, hour=hour, min=min, sec=sec, tz_hour=tz_hour, tz_min=tz_min } >end - __teliva_timestamp: >Fri Apr 22 23:47:55 2022 parse_time: >function parse_time(t) > local year, month, day, hour, min, sec, tz_op, tz_hour, tz_min = t:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)[Tt](%d%d%.?%d*):(%d%d):(%d%d)([+-])(%d%d):(%d%d)") > if tz_op == '+' then > hour = int(hour) + int(tz_hour) > min = int(min) + int(tz_min) > else > hour = int(hour) - int(tz_hour) > min = int(min) - int(tz_min) > end > return os.time{ year=int(year), month=int(month), day=int(day), hour=int(hour), min=int(min), sec=int(sec) } >end - __teliva_timestamp: >Fri Apr 22 23:48:46 2022 compare_parsed_time: >function compare_parsed_time(a, b) > return a.parsed_time < b.parsed_time >end > >-- unused >function compare_parsed_time2(a, b) > local a, b = a.parsed_time, b.parsed_time > if a.year ~= b.year then return a.year < b.year end > if a.month ~= b.month then return a.month < b.month end > if a.day ~= b.day then return a.day < b.day end > if a.hour ~= b.hour then return a.hour < b.hour end > if a.min ~= b.min then return a.min < b.min end > return a.sec < b.sec >end - __teliva_timestamp: >Fri Apr 22 23:49:13 2022 parse_time: >function parse_time(t) > local year, month, day, hour, min, sec, tz_op, tz_hour, tz_min = t:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)[Tt](%d%d%.?%d*):(%d%d):(%d%d)([+-])(%d%d):(%d%d)") > local int = tonumber > if tz_op == '+' then > hour = int(hour) + int(tz_hour) > min = int(min) + int(tz_min) > else > hour = int(hour) - int(tz_hour) > min = int(min) - int(tz_min) > end > return os.time{ year=int(year), month=int(month), day=int(day), hour=int(hour), min=int(min), sec=int(sec) } >end - __teliva_timestamp: >Fri Apr 22 23:51:08 2022 parse_time: >function parse_time(t) > local year, month, day, hour, min, sec, tz_op, tz_hour, tz_min = t:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)[Tt](%d%d%.?%d*):(%d%d):(%d%d)([+-])(%d%d):(%d%d)") > local int = tonumber > -- convert to UTC > if tz_op == '+' then > hour = int(hour) - int(tz_hour) > min = int(min) - int(tz_min) > else > hour = int(hour) + int(tz_hour) > min = int(min) + int(tz_min) > end > return os.time{ year=int(year), month=int(month), day=int(day), hour=hour, min=min, sec=int(sec) } >end - __teliva_timestamp: >Fri Apr 22 23:52:27 2022 __teliva_note: >parsing twtxt feeds and sorting items after accounting for timezones reload_feeds: >function reload_feeds() > for _, feed in ipairs(Feeds) do > local response, status, headers = http.request(feed) > print(status) > if status == 200 then > Window:clear() > parse_items(feed, response) > local all_items = {} > for feed, items in pairs(Items) do > for _, item in ipairs(items) do > table.insert(all_items, item) > end > end > table.sort(all_items, function(a, b) return a.parsed_time < b.parsed_time end) > for _, x in ipairs(all_items) do > print(x.feed, x.time, x.status) > end > Window:getch() > end > end >end - __teliva_timestamp: >Fri Apr 22 23:57:10 2022 update: >function update(window) > local key = window:getch() > if key == 18 then -- ctrl-r > reload_feeds(window) > end >end - __teliva_timestamp: >Sat Apr 23 00:13:26 2022 render_feed_status: >function render_feed_status(window, fs) > window:clear() > for _, fs in ipairs(feed_status) do > if fs.status then > if fs.status/100 == 2 then > window:attrset(curses.color_pair(10)) > window:addstr(fs.feed..'\n') > window:attrset(curses.A_NORMAL) > else > window:attrset(curses.color_pair(9)) > window:addstr(fs.feed..'\n') > window:attrset(curses.A_NORMAL) > end > else > window:addstr(fs.feed..'\n') > end > end > window:refresh() >end - __teliva_timestamp: >Sat Apr 23 00:18:59 2022 render: >function render(window) > window:clear() > -- draw feeds > for i, feed in ipairs(Feeds) do > print(feed) > end > -- draw items > local all_items = {} > local n = 0 > for feed, items in pairs(Items) do > n = n+1 > for _, item in ipairs(items) do > table.insert(all_items, item) > end > end > table.sort(all_items, function(a, b) return a.parsed_time < b.parsed_time end) > for _, x in ipairs(all_items) do > print(x.feed, x.time, x.status) > end > window:refresh() >end - __teliva_timestamp: >Sat Apr 23 00:20:31 2022 __teliva_note: >show progress when downloading feeds reload_feeds: >function reload_feeds(window) > window:nodelay(true) > feed_status = {} > for _, feed in ipairs(Feeds) do > table.insert(feed_status, {feed=feed}) > end > for _, fs in ipairs(feed_status) do > local response, status, headers = http.request(fs.feed) > fs.status = status > parse_items(fs.feed, response) > render_feed_status(window, fs) > if window:getch() then break end > end > window:nodelay(false) >end - __teliva_timestamp: >Sat Apr 23 00:28:09 2022 render: >function render(window) > window:clear() > -- draw feeds > print(str(#Feeds)..' feeds') > -- draw items > local all_items = {} > local n = 0 > for feed, items in pairs(Items) do > n = n+1 > for _, item in ipairs(items) do > table.insert(all_items, item) > end > end > table.sort(all_items, function(a, b) return a.parsed_time < b.parsed_time end) > for _, x in ipairs(all_items) do > print(x.feed, x.time, x.status) > end > window:refresh() >end - __teliva_timestamp: >Sat Apr 23 00:29:10 2022 reload_feeds: >function reload_feeds(window) > window:nodelay(true) > feed_status = {} > for _, feed in ipairs(Feeds) do > table.insert(feed_status, {feed=feed}) > end > render_feed_status( > for _, fs in ipairs(feed_status) do > local response, status, headers = http.request(fs.feed) > fs.status = status > parse_items(fs.feed, response) > render_feed_status(window, fs) > if window:getch() then break end > end > window:nodelay(false) >end - __teliva_timestamp: >Sat Apr 23 00:29:23 2022 render_feed_status: >function render_feed_status(window, feed_status) > window:clear() > for _, fs in ipairs(feed_status) do > if fs.status then > if fs.status/100 == 2 then > window:attrset(curses.color_pair(10)) > window:addstr(fs.feed..'\n') > window:attrset(curses.A_NORMAL) > else > window:attrset(curses.color_pair(9)) > window:addstr(fs.feed..'\n') > window:attrset(curses.A_NORMAL) > end > else > window:addstr(fs.feed..'\n') > end > end > window:refresh() >end - __teliva_timestamp: >Sat Apr 23 00:29:48 2022 reload_feeds: >function reload_feeds(window) > window:nodelay(true) > local feed_status = {} > for _, feed in ipairs(Feeds) do > table.insert(feed_status, {feed=feed}) > end > render_feed_status(feed_status) > for _, fs in ipairs(feed_status) do > local response, status, headers = http.request(fs.feed) > fs.status = status > parse_items(fs.feed, response) > render_feed_status(window, feed_status) > if window:getch() then break end > end > window:nodelay(false) >end - __teliva_timestamp: >Sat Apr 23 00:30:18 2022 __teliva_note: >fix an accidental global variable reload_feeds: >function reload_feeds(window) > window:nodelay(true) > local feed_status = {} > for _, feed in ipairs(Feeds) do > table.insert(feed_status, {feed=feed}) > end > render_feed_status(window, feed_status) > for _, fs in ipairs(feed_status) do > local response, status, headers = http.request(fs.feed) > fs.status = status > parse_items(fs.feed, response) > render_feed_status(window, feed_status) > if window:getch() then break end > end > window:nodelay(false) >end - __teliva_timestamp: >Sat Apr 23 00:45:54 2022 __teliva_note: >ignore non-compliant "comments" containing tabs >https://merveilles.town/@akkartik/108179923032818671 parse_items: >function parse_items(feed, lines) > if Items[feed] == nil then Items[feed] = {} end > for line in lines:gmatch('[^\n]+') do > local t, status = line:match('([0-9][^%s]+)\t(.*)') > if t and status then > table.insert(Items[feed], {feed=feed, time=t, parsed_time=parse_time(t), status=status}) > end > end > table.sort(Items[feed], compare_parsed_time) >end - __teliva_timestamp: >Sat Apr 23 00:52:39 2022 render_feed_status: >function render_feed_status(window, feed_status) > window:clear() > for _, fs in ipairs(feed_status) do > if fs.status then > if fs.status == 2 then > window:attrset(curses.color_pair(10)) > window:addstr(fs.feed..'\n') > window:attrset(curses.A_NORMAL) > else > window:attrset(curses.color_pair(9)) > window:addstr(fs.feed..'\n') > window:attrset(curses.A_NORMAL) > end > else > window:addstr(fs.feed..'\n') > end > end > window:refresh() >end - __teliva_timestamp: >Sat Apr 23 00:56:34 2022 __teliva_note: >handle errors in requests reload_feeds: >function reload_feeds(window) > window:nodelay(true) > local feed_status = {} > for _, feed in ipairs(Feeds) do > table.insert(feed_status, {feed=feed}) > end > render_feed_status(window, feed_status) > for _, fs in ipairs(feed_status) do > local response, status, headers = http.request(fs.feed) > if response == nil then > fs.status = 4 -- treat any network issue like a 404 > else > fs.status = status/100 > if fs.status == 2 then > parse_items(fs.feed, response) > end > render_feed_status(window, feed_status) > end > if window:getch() then break end > end > window:nodelay(false) >end - __teliva_timestamp: >Sat Apr 23 01:06:55 2022 __teliva_note: >handle more time formats parse_time: >function parse_time(t) > local int = tonumber > -- hackily strip out fractions of seconds > local year, month, day, hour, min, sec, tz_op, tz_hour, tz_min = t:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)[Tt](%d%d%.?%d*):(%d%d):(%d%d)%.?[0-9]*([+-])(%d%d):(%d%d)") > if year == nil then > year, month, day, hour, min, sec = t:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)[Tt](%d%d%.?%d*):(%d%d):(%d%d)%.?[0-9]*[Zz]") > end > if year == nil then > return 0 > end > -- convert to UTC > if tz_op == '+' then > hour = int(hour) - int(tz_hour) > min = int(min) - int(tz_min) > elseif tz_op == '-' then > hour = int(hour) + int(tz_hour) > min = int(min) + int(tz_min) > end > return os.time{ year=int(year), month=int(month), day=int(day), hour=hour, min=min, sec=int(sec) } >end > >function test_parse_time() > parse_time('2019-07-31T14:19+01:00') -- invalid per https://www.ietf.org/rfc/rfc3339.txt, but seen in the while; just ignore > parse_time('2019-09-16T01:13:07-07:00') > parse_time('2019-09-16T01:13:07Z') >end ================================================ FILE: src/Makefile ================================================ # makefile for building Lua # see ../INSTALL for installation instructions # see ../Makefile and luaconf.h for further customization # == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT ======================= # Your platform. See PLATS for possible values. PLAT= none CC= gcc CFLAGS= -g -O2 -std=c99 -Wall $(MYCFLAGS) -D_DEFAULT_SOURCE AR= ar rc RANLIB= ranlib RM= rm -f LIBS= -lm $(MYLIBS) -lssl -lcrypto MYCFLAGS= MYLDFLAGS= MYLIBS= # == END OF USER SETTINGS. NO NEED TO CHANGE ANYTHING BELOW THIS LINE ========= # Keep this sync'd with src/Makefile PLATS= freebsd linux macosx netbsd openbsd LUA_A= liblua.a CORE_O= lapi.o lcode.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o \ lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o \ lundump.o lvm.o lzio.o \ realpath.o kilo.o tlv.o teliva.o LIB_O= lauxlib.o lbaselib.o liolib.o lmathlib.o loslib.o \ ltablib.o lstrlib.o linit.o LUA_T= teliva LUA_O= lua.o ALL_O= $(CORE_O) $(LIB_O) $(LUA_O) ALL_T= $(LUA_A) $(LUA_T) ALL_A= $(LUA_A) default: $(PLAT) all: $(ALL_T) o: $(ALL_O) a: $(ALL_A) $(LUA_A): $(CORE_O) $(LIB_O) $(AR) $@ $(CORE_O) $(LIB_O) $(RANLIB) $@ $(LUA_T): $(LUA_O) $(LUA_A) lcurses/curses.o luasocket/socket.a luasocket/mime.a luasec/ssl.a $(CC) -o $@ $(MYLDFLAGS) $(LUA_O) $(LUA_A) lcurses/curses.o luasocket/socket.a luasocket/mime.a luasec/ssl.a $(LIBS) clean: $(RM) lcurses/curses.o $(MAKE) -C luasocket clean $(MAKE) -C luasec clean $(RM) $(ALL_T) $(ALL_O) depend: @$(CC) $(CFLAGS) -MM l*.c print.c echo: @echo "PLAT = $(PLAT)" @echo "CC = $(CC)" @echo "CFLAGS = $(CFLAGS)" @echo "AR = $(AR)" @echo "RANLIB = $(RANLIB)" @echo "RM = $(RM)" @echo "MYCFLAGS = $(MYCFLAGS)" @echo "MYLDFLAGS = $(MYLDFLAGS)" @echo "MYLIBS = $(MYLIBS)" # convenience targets for popular platforms none: @echo "Please choose a platform:" @echo " $(PLATS)" freebsd: $(MAKE) -C luasocket bsd $(MAKE) -C luasec bsd $(MAKE) all MYLIBS="-Wl,-E -lncursesw" linux: $(MAKE) -C lcurses CC="$(CC)" CFLAGS="$(CFLAGS)" $(MAKE) -C luasocket linux $(MAKE) -C luasec linux $(MAKE) all MYCFLAGS=-Wpedantic MYLIBS="-Wl,-E -lncursesw" macosx: $(MAKE) -C lcurses CC="$(CC)" CFLAGS="$(CFLAGS)" $(MAKE) -C luasocket macosx $(MAKE) -C luasec macosx $(MAKE) all MYCFLAGS=-Wpedantic MYLIBS="-L/usr/local/opt/openssl@3/lib -lncurses" openbsd: $(MAKE) -C lcurses CC="$(CC)" CFLAGS="$(CFLAGS)" $(MAKE) -C luasocket bsd $(MAKE) -C luasec bsd $(MAKE) all MYLIBS="-Wl,-E -lncurses" netbsd: $(MAKE) -C lcurses CC="$(CC)" CFLAGS="$(CFLAGS)" $(MAKE) -C luasocket bsd $(MAKE) -C luasec bsd $(MAKE) all MYLIBS="-Wl,-E -lcurses" # list targets that do not create files (but not all makes understand .PHONY) .PHONY: all $(PLATS) default o a clean depend echo none # DO NOT DELETE lapi.o: lapi.c lua.h luaconf.h lapi.h lobject.h llimits.h ldebug.h \ lstate.h ltm.h lzio.h lmem.h ldo.h lfunc.h lgc.h lstring.h ltable.h \ lundump.h lvm.h lauxlib.o: lauxlib.c lua.h luaconf.h lauxlib.h lbaselib.o: lbaselib.c lua.h luaconf.h lauxlib.h lualib.h lcode.o: lcode.c lua.h luaconf.h lcode.h llex.h lobject.h llimits.h \ lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h ldo.h lgc.h \ ltable.h ldebug.o: ldebug.c lua.h luaconf.h lapi.h lobject.h llimits.h lcode.h \ llex.h lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h ldo.h \ lfunc.h lstring.h lgc.h ltable.h lvm.h ldo.o: ldo.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h ltm.h \ lzio.h lmem.h ldo.h lfunc.h lgc.h lopcodes.h lparser.h lstring.h \ ltable.h lundump.h lvm.h ldump.o: ldump.c lua.h luaconf.h lobject.h llimits.h lstate.h ltm.h \ lzio.h lmem.h lundump.h lfunc.o: lfunc.c lua.h luaconf.h lfunc.h lobject.h llimits.h lgc.h lmem.h \ lstate.h ltm.h lzio.h lgc.o: lgc.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h ltm.h \ lzio.h lmem.h ldo.h lfunc.h lgc.h lstring.h ltable.h linit.o: linit.c lua.h luaconf.h lualib.h lauxlib.h liolib.o: liolib.c lua.h luaconf.h lauxlib.h lualib.h llex.o: llex.c lua.h luaconf.h ldo.h lobject.h llimits.h lstate.h ltm.h \ lzio.h lmem.h llex.h lparser.h lstring.h lgc.h ltable.h lmathlib.o: lmathlib.c lua.h luaconf.h lauxlib.h lualib.h lmem.o: lmem.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h \ ltm.h lzio.h lmem.h ldo.h lobject.o: lobject.c lua.h luaconf.h ldo.h lobject.h llimits.h lstate.h \ ltm.h lzio.h lmem.h lstring.h lgc.h lvm.h lopcodes.o: lopcodes.c lopcodes.h llimits.h lua.h luaconf.h loslib.o: loslib.c lua.h luaconf.h lauxlib.h lualib.h lparser.o: lparser.c lua.h luaconf.h lcode.h llex.h lobject.h llimits.h \ lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h ldo.h \ lfunc.h lstring.h lgc.h ltable.h lstate.o: lstate.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h \ ltm.h lzio.h lmem.h ldo.h lfunc.h lgc.h llex.h lstring.h ltable.h lstring.o: lstring.c lua.h luaconf.h lmem.h llimits.h lobject.h lstate.h \ ltm.h lzio.h lstring.h lgc.h lstrlib.o: lstrlib.c lua.h luaconf.h lauxlib.h lualib.h ltable.o: ltable.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h \ ltm.h lzio.h lmem.h ldo.h lgc.h ltable.h ltablib.o: ltablib.c lua.h luaconf.h lauxlib.h lualib.h ltm.o: ltm.c lua.h luaconf.h lobject.h llimits.h lstate.h ltm.h lzio.h \ lmem.h lstring.h lgc.h ltable.h lua.o: lua.c lua.h luaconf.h lauxlib.h lualib.h teliva.h lundump.o: lundump.c lua.h luaconf.h ldebug.h lstate.h lobject.h \ llimits.h ltm.h lzio.h lmem.h ldo.h lfunc.h lstring.h lgc.h lundump.h lvm.o: lvm.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h ltm.h \ lzio.h lmem.h ldo.h lfunc.h lgc.h lopcodes.h lstring.h ltable.h lvm.h lzio.o: lzio.c lua.h luaconf.h llimits.h lmem.h lstate.h lobject.h ltm.h \ lzio.h print.o: print.c ldebug.h lstate.h lua.h luaconf.h lobject.h llimits.h \ ltm.h lzio.h lmem.h lopcodes.h lundump.h realpath.o: realpath.c kilo.o: kilo.c lua.h teliva.h teliva.o: teliva.c lua.h lauxlib.h lualib.h teliva.h tlv.h # (end of Makefile) ================================================ FILE: src/file.lua ================================================ -- primitive for reading files from a file system (or, later, network) -- returns an object or nil on error -- read lines from the object using .read() with args similar to file:read() -- read() will indicate end of file by returning nil. function start_reading(fs, filename) local infile = io.open(filename) if infile == nil then return nil end return { read = coroutine.wrap(function(format) while true do if format == nil then format = '*l' end format = coroutine.yield(infile:read(format)) end end), } end -- fake file object with the same 'shape' as that returned by start_reading function fake_file_stream(s) local i = 1 local max = string.len(s) return { read = function(format) if i > max then return nil end if type(format) == 'number' then local result = s:sub(i, i+format-1) i = i+format return result elseif format == '*a' then local result = s:sub(i) i = max+1 return result elseif format == '*l' then local start = i while i <= max do if s:sub(i, i) == '\n' then break end i = i+1 end local result = s:sub(start, i) i = i+1 return result elseif format == '*n' then error('fake file streams: *n not yet supported') end end, } end function test_fake_file_system() local s = fake_file_stream('abcdefgh\nijk\nlmn') check_eq(s.read(1), 'a', 'fake_file_system: 1 char') check_eq(s.read(1), 'b', 'fake_file_system: 1 more char') check_eq(s.read(3), 'cde', 'fake_file_system: multiple chars') check_eq(s.read('*l'), 'fgh\n', 'fake_file_system: line') check_eq(s.read('*a'), 'ijk\nlmn', 'fake_file_system: all') end -- primitive for writing files to a file system (or, later, network) -- returns an object or nil on error -- write to the object using .write() -- indicate you're done writing by calling .close() -- file will not be externally visible until .close() function start_writing(fs, filename) if filename == nil then error('start_writing requires two arguments: a file-system (nil for real disk) and a filename') end local initial_filename = temporary_filename_in_same_volume(filename) local outfile = io.open(initial_filename, 'w') if outfile == nil then return nil end return { write = coroutine.wrap(function(x) while true do x = coroutine.yield(outfile:write(x)) end end), close = function() outfile:close() os.rename(initial_filename, filename) end, } end function temporary_filename_in_same_volume(filename) -- opening in same directory will hopefully keep it on the same volume, -- so that a future rename works local i = 1 while true do temporary_filename = 'teliva_tmp_'..filename..'_'..i if io.open(temporary_filename) == nil then -- file doesn't exist yet; create a placeholder and return it local handle = io.open(temporary_filename, 'w') if handle == nil then -- HERE: if an app doesn't have permissions, attempts to write to disk eventually get here error("this is unexpected; I can't create temporary files..") end handle:close() return temporary_filename end i = i+1 end end ================================================ FILE: src/json.lua ================================================ -- -- https://github.com/rxi/json.lua -- -- Copyright (c) 2020 rxi -- -- 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 json = { _version = "0.1.2" } ------------------------------------------------------------------------------- -- Encode ------------------------------------------------------------------------------- local encode local escape_char_map = { [ "\\" ] = "\\", [ "\"" ] = "\"", [ "\b" ] = "b", [ "\f" ] = "f", [ "\n" ] = "n", [ "\r" ] = "r", [ "\t" ] = "t", } local escape_char_map_inv = { [ "/" ] = "/" } for k, v in pairs(escape_char_map) do escape_char_map_inv[v] = k end local function escape_char(c) return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) end local function encode_nil(val) return "null" end local function encode_table(val, stack) local res = {} stack = stack or {} -- Circular reference? if stack[val] then error("circular reference") end stack[val] = true if rawget(val, 1) ~= nil or next(val) == nil then -- Treat as array -- check keys are valid and it is not sparse local n = 0 for k in pairs(val) do if type(k) ~= "number" then error("invalid table: mixed or invalid key types") end n = n + 1 end if n ~= #val then error("invalid table: sparse array") end -- Encode for i, v in ipairs(val) do table.insert(res, encode(v, stack)) end stack[val] = nil return "[" .. table.concat(res, ",") .. "]" else -- Treat as an object for k, v in pairs(val) do if type(k) ~= "string" then error("invalid table: mixed or invalid key types") end table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) end stack[val] = nil return "{" .. table.concat(res, ",") .. "}" end end local function encode_string(val) return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' end local function encode_number(val) -- Check for NaN, -inf and inf if val ~= val or val <= -math.huge or val >= math.huge then error("unexpected number value '" .. tostring(val) .. "'") end return string.format("%.14g", val) end local type_func_map = { [ "nil" ] = encode_nil, [ "table" ] = encode_table, [ "string" ] = encode_string, [ "number" ] = encode_number, [ "boolean" ] = tostring, } encode = function(val, stack) local t = type(val) local f = type_func_map[t] if f then return f(val, stack) end error("unexpected type '" .. t .. "'") end function json.encode(val) return ( encode(val) ) end ------------------------------------------------------------------------------- -- Decode ------------------------------------------------------------------------------- local parse local function create_set(...) local res = {} for i = 1, select("#", ...) do res[ select(i, ...) ] = true end return res end local space_chars = create_set(" ", "\t", "\r", "\n") local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") local literals = create_set("true", "false", "null") local literal_map = { [ "true" ] = true, [ "false" ] = false, [ "null" ] = nil, } local function next_char(str, idx, set, negate) for i = idx, #str do if set[str:sub(i, i)] ~= negate then return i end end return #str + 1 end local function decode_error(str, idx, msg) local line_count = 1 local col_count = 1 for i = 1, idx - 1 do col_count = col_count + 1 if str:sub(i, i) == "\n" then line_count = line_count + 1 col_count = 1 end end error( string.format("%s at line %d col %d", msg, line_count, col_count) ) end local function codepoint_to_utf8(n) -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa local f = math.floor if n <= 0x7f then return string.char(n) elseif n <= 0x7ff then return string.char(f(n / 64) + 192, n % 64 + 128) elseif n <= 0xffff then return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) elseif n <= 0x10ffff then return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, f(n % 4096 / 64) + 128, n % 64 + 128) end error( string.format("invalid unicode codepoint '%x'", n) ) end local function parse_unicode_escape(s) local n1 = tonumber( s:sub(1, 4), 16 ) local n2 = tonumber( s:sub(7, 10), 16 ) -- Surrogate pair? if n2 then return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) else return codepoint_to_utf8(n1) end end local function parse_string(str, i) local res = "" local j = i + 1 local k = j while j <= #str do local x = str:byte(j) if x < 32 then decode_error(str, j, "control character in string") elseif x == 92 then -- `\`: Escape res = res .. str:sub(k, j - 1) j = j + 1 local c = str:sub(j, j) if c == "u" then local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) or str:match("^%x%x%x%x", j + 1) or decode_error(str, j - 1, "invalid unicode escape in string") res = res .. parse_unicode_escape(hex) j = j + #hex else if not escape_chars[c] then decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") end res = res .. escape_char_map_inv[c] end k = j + 1 elseif x == 34 then -- `"`: End of string res = res .. str:sub(k, j - 1) return res, j + 1 end j = j + 1 end decode_error(str, i, "expected closing quote for string") end local function parse_number(str, i) local x = next_char(str, i, delim_chars) local s = str:sub(i, x - 1) local n = tonumber(s) if not n then decode_error(str, i, "invalid number '" .. s .. "'") end return n, x end local function parse_literal(str, i) local x = next_char(str, i, delim_chars) local word = str:sub(i, x - 1) if not literals[word] then decode_error(str, i, "invalid literal '" .. word .. "'") end return literal_map[word], x end local function parse_array(str, i) local res = {} local n = 1 i = i + 1 while 1 do local x i = next_char(str, i, space_chars, true) -- Empty / end of array? if str:sub(i, i) == "]" then i = i + 1 break end -- Read token x, i = parse(str, i) res[n] = x n = n + 1 -- Next token i = next_char(str, i, space_chars, true) local chr = str:sub(i, i) i = i + 1 if chr == "]" then break end if chr ~= "," then decode_error(str, i, "expected ']' or ','") end end return res, i end local function parse_object(str, i) local res = {} i = i + 1 while 1 do local key, val i = next_char(str, i, space_chars, true) -- Empty / end of object? if str:sub(i, i) == "}" then i = i + 1 break end -- Read key if str:sub(i, i) ~= '"' then decode_error(str, i, "expected string for key") end key, i = parse(str, i) -- Read ':' delimiter i = next_char(str, i, space_chars, true) if str:sub(i, i) ~= ":" then decode_error(str, i, "expected ':' after key") end i = next_char(str, i + 1, space_chars, true) -- Read value val, i = parse(str, i) -- Set res[key] = val -- Next token i = next_char(str, i, space_chars, true) local chr = str:sub(i, i) i = i + 1 if chr == "}" then break end if chr ~= "," then decode_error(str, i, "expected '}' or ','") end end return res, i end local char_func_map = { [ '"' ] = parse_string, [ "0" ] = parse_number, [ "1" ] = parse_number, [ "2" ] = parse_number, [ "3" ] = parse_number, [ "4" ] = parse_number, [ "5" ] = parse_number, [ "6" ] = parse_number, [ "7" ] = parse_number, [ "8" ] = parse_number, [ "9" ] = parse_number, [ "-" ] = parse_number, [ "t" ] = parse_literal, [ "f" ] = parse_literal, [ "n" ] = parse_literal, [ "[" ] = parse_array, [ "{" ] = parse_object, } parse = function(str, idx) local chr = str:sub(idx, idx) local f = char_func_map[chr] if f then return f(str, idx) end decode_error(str, idx, "unexpected character '" .. chr .. "'") end function json.decode(str) if type(str) ~= "string" then error("expected argument of type string, got " .. type(str)) end local res, idx = parse(str, next_char(str, 1, space_chars, true)) idx = next_char(str, idx, space_chars, true) if idx <= #str then decode_error(str, idx, "trailing garbage") end return res end return json ================================================ FILE: src/jsonf.lua ================================================ -- -- variant of https://github.com/rxi/json.lua decoding from channels of -- characters rather than strings -- -- Copyright (c) 2020 rxi -- -- 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 jsonf = { _version = "0.1.2" } local escape_char_map = { [ "\\" ] = "\\", [ "\"" ] = "\"", [ "\b" ] = "b", [ "\f" ] = "f", [ "\n" ] = "n", [ "\r" ] = "r", [ "\t" ] = "t", } local escape_char_map_inv = { [ "/" ] = "/" } for k, v in pairs(escape_char_map) do escape_char_map_inv[v] = k end ------------------------------------------------------------------------------- -- Decode ------------------------------------------------------------------------------- local function create_set(...) local res = {} for i = 1, select("#", ...) do res[ select(i, ...) ] = true end return res end local space_chars = create_set(" ", "\t", "\r", "\n") local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") local literals = create_set("true", "false", "null") local literal_map = { [ "true" ] = true, [ "false" ] = false, [ "null" ] = nil, } local function skip_spaces(infile) while true do local c = infile.read(1) if c == nil then break end if space_chars[c] == nil then return c end end return nil end local function next_chars(infile, set, firstc) local res = {firstc} local nextc while true do nextc = infile.read(1) if nextc == nil then break end if set[nextc] then break end table.insert(res, nextc) end return table.concat(res), nextc end local function codepoint_to_utf8(n) -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa local f = math.floor if n <= 0x7f then return string.char(n) elseif n <= 0x7ff then return string.char(f(n / 64) + 192, n % 64 + 128) elseif n <= 0xffff then return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) elseif n <= 0x10ffff then return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, f(n % 4096 / 64) + 128, n % 64 + 128) end error( string.format("invalid unicode codepoint '%x'", n) ) end local function parse_unicode_escape(s) local n1 = tonumber( s:sub(1, 4), 16 ) local n2 = tonumber( s:sub(7, 10), 16 ) -- Surrogate pair? if n2 then return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) else return codepoint_to_utf8(n1) end end local function parse_string(infile, firstc) local res = {} while true do local chr = infile.read(1) if chr == nil then break end local x = chr:byte() if x < 32 then error("control character in string") elseif chr == '\\' then local c = infile.read(1) if c == nil then break end if c == "u" then local hex = '' c = infile.read(1) if c == nil then break end hex = hex..c c = infile.read(1) if c == nil then break end hex = hex..c c = infile.read(1) if c == nil then break end hex = hex..c c = infile.read(1) if c == nil then break end hex = hex..c if not hex:match('^%x%x%x%x') then error('invalid unicode escape in string') end table.insert(res, parse_unicode_escape(hex)) else if not escape_chars[c] then error("invalid escape char '" .. c .. "' in string") end table.insert(res, escape_char_map_inv[c]) end elseif chr == '"' then return table.concat(res), infile.read(1) else table.insert(res, chr) end end error("expected closing quote for string") end local function parse_number(infile, firstc) --? print('parse_number') local res = {firstc} local nextc while true do nextc = infile.read(1) if nextc == nil then break end if delim_chars[nextc] then break end table.insert(res, nextc) end local s = table.concat(res) --? print('parse_number: '..s) local n = tonumber(s) if not n then error("invalid number '" .. s .. "'") end return n, nextc end local function parse_literal(infile, firstc) --? print('parse_literal') local word, nextc = next_chars(infile, delim_chars, firstc) if not literals[word] then error("invalid literal '" .. word .. "'") end --? print('parse_literal: '..word) return literal_map[word], nextc end local function parse_array(infile, firstc) local res = {} local x, nextc while true do nextc = skip_spaces(infile) if nextc == nil then error("expected ']' or ','") end if nextc == ']' then break end -- empty array -- Read token x, nextc = parse(infile, nextc) --? print('array elem: '..str(x)) table.insert(res, x) -- Next token if space_chars[nextc] then nextc = skip_spaces(infile) end if nextc == ']' then break end if nextc ~= ',' then error("expected ']' or ','") end end return res, skip_spaces(infile) end local function parse_object(infile, firstc) local res = {} local nextc while true do local key, val nextc = skip_spaces(infile) if nextc == nil then error("expected '}' or ','") end if nextc == '}' then break end -- empty object -- Read key if nextc ~= '"' then error("expected string for key") end key, nextc = parse(infile, nextc) --? print('object key: '..key) -- Read ':' delimiter if space_chars[nextc] then nextc = skip_spaces(infile) end if nextc ~= ':' then error("expected ':' after key") end -- Read value nextc = skip_spaces(infile) val, nextc = parse(infile, nextc) --? print('object val: '..str(val)) -- Set res[key] = val -- Next token if space_chars[nextc] then nextc = skip_spaces(infile) end if nextc == '}' then break end if nextc ~= ',' then error("expected '}' or ','") end end return res, skip_spaces(infile) end local char_func_map = { [ '"' ] = parse_string, [ "0" ] = parse_number, [ "1" ] = parse_number, [ "2" ] = parse_number, [ "3" ] = parse_number, [ "4" ] = parse_number, [ "5" ] = parse_number, [ "6" ] = parse_number, [ "7" ] = parse_number, [ "8" ] = parse_number, [ "9" ] = parse_number, [ "-" ] = parse_number, [ "t" ] = parse_literal, [ "f" ] = parse_literal, [ "n" ] = parse_literal, [ "[" ] = parse_array, [ "{" ] = parse_object, } parse = function(infile, chr) local f = char_func_map[chr] if f then return f(infile, chr) end error("unexpected character '" .. chr .. "'") end function jsonf.decode(infile) local firstc = skip_spaces(infile) local res, nextc = parse(infile, firstc) if nextc then error("trailing garbage") end return res end -- test cases: -- "abc" -- 234 -- true -- false -- nil -- ["abc", 234, true, false, nil] -- ["abc", 234, true, false, nil -- ["abc", -- {"abc": 234, "def": true} return jsonf ================================================ FILE: src/kilo.c ================================================ /* Based on https://github.com/antirez/kilo * * ----------------------------------------------------------------------- * * Copyright (C) 2016 Salvatore Sanfilippo * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define KILO_VERSION "0.0.1" #include #ifdef __NetBSD__ #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "lua.h" #include "teliva.h" /* Syntax highlight types */ #define HL_NORMAL 0 #define HL_NONPRINT 1 #define HL_COMMENT 2 /* Single line comment. */ #define HL_MLCOMMENT 3 /* Multi-line comment. */ #define HL_KEYWORD1 4 #define HL_KEYWORD2 5 #define HL_STRING 6 #define HL_NUMBER 7 #define HL_MATCH 8 /* Search match. */ #define HL_SELECTABLE 9 #define HL_SELECTABLE_BORDER 10 struct editorSyntax { char **keywords; char *singleline_comment_start; char *multiline_comment_start; char *multiline_comment_end; char *selectable_start; char *selectable_end; }; /* This structure represents a single line of the file we are editing. */ typedef struct erow { int idx; /* Row index in the file, zero-based. */ int size; /* Size of the row, excluding the null term. */ int rsize; /* Size of the rendered row. */ char *chars; /* Row content. */ char *render; /* Row content "rendered" for screen (for TABs). */ unsigned char *hl; /* Syntax highlight type for each character in render.*/ int hl_oc; /* Row had open comment at end in last syntax highlight check. */ } erow; typedef struct hlcolor { int r,g,b; } hlcolor; #define LINE_NUMBER_SPACE 4 #define MENU_SPACE 1 #define CALLERS_SPACE 2 struct editorConfig { int cx,cy; /* Cursor x and y position in characters relative to top-left of viewport */ int startcol, cols; /* viewport column bounds */ int startrow, endrow; /* viewport row bounds */ int rowoff; /* Offset of row displayed. */ int coloff; /* Offset of column displayed. */ int numrows; /* Number of rows */ erow *row; /* Rows */ int dirty; /* File modified but not saved. */ char *filename; /* Currently open filename */ struct editorSyntax *syntax; /* Current syntax highlight, or NULL. */ }; static struct editorConfig E; /* =========================== Syntax highlights DB ========================= * * In order to add a new syntax, define two arrays with a list of file name * matches and keywords. The file name matches are used in order to match * a given syntax with a given file name: if a match pattern starts with a * dot, it is matched as the last past of the filename, for example ".c". * Otherwise the pattern is just searched inside the filenme, like "Makefile"). * * The list of keywords to highlight is just a list of words, however if they * a trailing '|' character is added at the end, they are highlighted in * a different color, so that you can have two different sets of keywords. * * Finally add a stanza in the HLDB global variable with two two arrays * of strings. * * The characters for single and multi line comments must be exactly two * and must be provided as well (see the C language example). * * There is no support to highlight patterns currently. */ /* Lua */ char *Lua_HL_keywords[] = { /* keywords */ "do", "end", "function", "return", "require", "local", "if", "then", "else", "elseif", "while", "for", "repeat", "until", "break", "and", "or", "not", "in", /* types */ "nil|", "false|", "true|", NULL }; struct editorSyntax LuaSyntax = { Lua_HL_keywords, "--", /* line comment */ "--[[", "--]]", /* multiline comment */ NULL, NULL /* no selectables */ }; /* Prose */ struct editorSyntax ProseSyntax = { NULL, /* no keywords */ NULL, /* no line comment */ NULL, NULL, /* no multiline comment */ "[[", "]]" /* "selectables" */ }; #define HLDB_ENTRIES (sizeof(HLDB)/sizeof(HLDB[0])) /* ====================== Syntax highlight color scheme ==================== */ static int is_separator(int c) { return c == '\0' || isspace(c) || strchr(",.()+-/*=~%[];",c) != NULL; } /* Return true if the specified row last char is part of a multi line comment * that starts at this row or at one before, and does not end at the end * of the row but spawns to the next row. */ static int editorRowHasOpenComment(erow *row, char *mce) { if (row->hl && row->rsize && row->hl[row->rsize-1] == HL_MLCOMMENT && (row->rsize < strlen(mce) || strcmp(&row->render[row->rsize-strlen(mce)], mce) != 0)) return 1; return 0; } /* Set every byte of row->hl (that corresponds to every character in the line) * to the right syntax highlight type (HL_* defines). */ static void editorUpdateSyntax(erow *row) { row->hl = realloc(row->hl,row->rsize); memset(row->hl,HL_NORMAL,row->rsize); if (E.syntax == NULL) return; /* No syntax, everything is HL_NORMAL. */ int i, prev_sep, in_string, in_comment, in_selectable; char *p; char **keywords = E.syntax->keywords; char *scs = E.syntax->singleline_comment_start; char *mcs = E.syntax->multiline_comment_start; char *mce = E.syntax->multiline_comment_end; char *ss = E.syntax->selectable_start; char *se = E.syntax->selectable_end; /* Point to the first non-space char. */ p = row->render; i = 0; /* Current char offset */ while(*p && isspace(*p)) { p++; i++; } prev_sep = 1; /* Tell the parser if 'i' points to start of word. */ in_string = 0; /* Are we inside "" or '' ? */ in_comment = 0; /* Are we inside multi-line comment? */ in_selectable = 0; /* Are we inside [[]] in prose? */ /* If the previous line has an open comment, this line starts * with an open comment state. */ if (row->idx > 0 && editorRowHasOpenComment(&E.row[row->idx-1], mce)) in_comment = 1; while(*p) { /* Handle multi line comments. */ if (mcs && mce) { if (in_comment) { row->hl[i] = HL_MLCOMMENT; if (starts_with(p, mce)) { memset(&row->hl[i],HL_MLCOMMENT, strlen(mce)); p += strlen(mce); i += strlen(mce); in_comment = 0; prev_sep = 1; continue; } else { prev_sep = 0; p++; i++; continue; } } else if (starts_with(p, mcs)) { memset(&row->hl[i],HL_MLCOMMENT, strlen(mcs)); p += strlen(mcs); i += strlen(mcs); in_comment = 1; prev_sep = 0; continue; } } /* Handle single-line comments. */ if (scs && prev_sep && starts_with(p, scs)) { /* From here to end is a comment */ memset(row->hl+i,HL_COMMENT,row->rsize-i); return; } /* Handle selectable widgets */ if (ss && se) { if (in_selectable) { if (starts_with(p, se)) { memset(&row->hl[i],HL_SELECTABLE_BORDER, strlen(se)); p += strlen(se); i += strlen(se); in_selectable = 0; prev_sep = 1; continue; } else { row->hl[i] = HL_SELECTABLE; prev_sep = 0; p++; i++; continue; } } else if (starts_with(p, ss)) { memset(&row->hl[i],HL_SELECTABLE_BORDER, strlen(ss)); p += strlen(ss); i += strlen(ss); in_selectable = 1; prev_sep = 0; continue; } } /* Handle "" and '' */ if (E.syntax == &LuaSyntax) { // obscene hack if (in_string) { row->hl[i] = HL_STRING; if (*p == '\\' && *(p+1)) { row->hl[i+1] = HL_STRING; p += 2; i += 2; prev_sep = 0; continue; } if (*p == in_string) in_string = 0; p++; i++; continue; } else { if (*p == '"' || *p == '\'') { in_string = *p; row->hl[i] = HL_STRING; p++; i++; prev_sep = 0; continue; } } } /* Handle non printable chars. */ if (!isprint(*p)) { row->hl[i] = HL_NONPRINT; p++; i++; prev_sep = 0; continue; } /* Handle numbers */ if ((isdigit(*p) && (prev_sep || row->hl[i-1] == HL_NUMBER)) || (*p == '.' && i > 0 && row->hl[i-1] == HL_NUMBER)) { row->hl[i] = HL_NUMBER; p++; i++; prev_sep = 0; continue; } /* Handle keywords and lib calls */ if (keywords && prev_sep) { int j; int ileft = row->rsize-i; for (j = 0; keywords[j]; j++) { int klen = strlen(keywords[j]); int kw2 = keywords[j][klen-1] == '|'; if (kw2) klen--; if (klen <= ileft && !memcmp(p,keywords[j],klen) && is_separator(*(p+klen))) { /* Keyword */ memset(row->hl+i,kw2 ? HL_KEYWORD2 : HL_KEYWORD1,klen); p += klen; i += klen; break; } } if (keywords[j] != NULL) { prev_sep = 0; continue; /* We had a keyword match */ } } /* Not special chars */ prev_sep = is_separator(*p); p++; i++; } /* Propagate syntax change to the next row if the open comment * state changed. This may recursively affect all the following rows * in the file. */ int oc = editorRowHasOpenComment(row, mce); if (row->hl_oc != oc && row->idx+1 < E.numrows) editorUpdateSyntax(&E.row[row->idx+1]); row->hl_oc = oc; } /* Maps syntax highlight token types to terminal colors. */ static int editorSyntaxToColorPair(int hl) { switch(hl) { case HL_COMMENT: case HL_MLCOMMENT: return COLOR_PAIR_LUA_COMMENT; case HL_KEYWORD1: return COLOR_PAIR_LUA_KEYWORD; case HL_KEYWORD2: return COLOR_PAIR_LUA_CONSTANT; case HL_STRING: return COLOR_PAIR_LUA_CONSTANT; case HL_NUMBER: return COLOR_PAIR_LUA_CONSTANT; case HL_MATCH: return COLOR_PAIR_MATCH; case HL_SELECTABLE: return COLOR_PAIR_SELECTABLE; case HL_SELECTABLE_BORDER: return COLOR_PAIR_FADE; default: return COLOR_PAIR_NORMAL; } } /* ======================= Editor rows implementation ======================= */ /* Update the rendered version and the syntax highlight of a row. */ static void editorUpdateRow(erow *row) { unsigned int tabs = 0, nonprint = 0; int j, idx; /* Create a version of the row we can directly print on the screen, * respecting tabs, substituting non printable characters with '?'. */ free(row->render); for (j = 0; j < row->size; j++) if (row->chars[j] == TAB) tabs++; unsigned long long allocsize = (unsigned long long) row->size + tabs*8 + nonprint*9 + 1; if (allocsize > UINT32_MAX) { printf("Some line of the edited file is too long for kilo\n"); exit(1); } row->render = malloc(row->size + tabs*8 + nonprint*9 + 1); idx = 0; for (j = 0; j < row->size; j++) { if (row->chars[j] == TAB) { row->render[idx++] = ' '; while((idx+1) % 8 != 0) row->render[idx++] = ' '; } else { row->render[idx++] = row->chars[j]; } } row->rsize = idx; row->render[idx] = '\0'; /* Update the syntax highlighting attributes of the row. */ editorUpdateSyntax(row); } /* Insert a row at the specified position, shifting the other rows on the bottom * if required. */ static void editorInsertRow(int at, char *s, size_t len) { if (at > E.numrows) return; E.row = realloc(E.row,sizeof(erow)*(E.numrows+1)); if (at != E.numrows) { memmove(E.row+at+1,E.row+at,sizeof(E.row[0])*(E.numrows-at)); for (int j = at+1; j <= E.numrows; j++) E.row[j].idx++; } E.row[at].size = len; E.row[at].chars = malloc(len+1); memcpy(E.row[at].chars,s,len+1); E.row[at].hl = NULL; E.row[at].hl_oc = 0; E.row[at].render = NULL; E.row[at].rsize = 0; E.row[at].idx = at; editorUpdateRow(E.row+at); E.numrows++; E.dirty++; } /* Free row's heap allocated stuff. */ static void editorFreeRow(erow *row) { free(row->render); free(row->chars); free(row->hl); } /* Remove the row at the specified position, shifting the remaining up. */ static void editorDelRow(int at) { erow *row; if (at >= E.numrows) return; row = E.row+at; editorFreeRow(row); memmove(E.row+at,E.row+at+1,sizeof(E.row[0])*(E.numrows-at-1)); for (int j = at; j < E.numrows-1; j++) E.row[j].idx--; E.numrows--; E.dirty++; } void clearEditor(void) { E.cx = E.cy = 0; E.rowoff = E.coloff = 0; for (int j = E.numrows-1; j >= 0; j--) editorDelRow(j); } /* Turn the editor rows into a single heap-allocated string. * Returns the pointer to the heap-allocated string and populate the * integer pointed by 'buflen' with the size of the string, excluding * the final null term. */ static char *editorRowsToString(int *buflen) { char *buf = NULL, *p; int totlen = 0; int j; /* Compute count of bytes */ for (j = 0; j < E.numrows; j++) totlen += E.row[j].size+1; /* +1 is for "\n" at end of every row */ *buflen = totlen; totlen++; /* Also make space for null term */ p = buf = malloc(totlen); for (j = 0; j < E.numrows; j++) { memcpy(p,E.row[j].chars,E.row[j].size); p += E.row[j].size; *p = '\n'; p++; } *p = '\0'; return buf; } /* Insert a character at the specified position in a row, moving the remaining * chars on the right if needed. */ static void editorRowInsertChar(erow *row, int at, int c) { if (at > row->size) { /* Pad the string with spaces if the insert location is outside the * current length by more than a single character. */ int padlen = at-row->size; /* In the next line +2 means: new char and null term. */ row->chars = realloc(row->chars,row->size+padlen+2); memset(row->chars+row->size,' ',padlen); row->chars[row->size+padlen+1] = '\0'; row->size += padlen+1; } else { /* If we are in the middle of the string just make space for 1 new * char plus the (already existing) null term. */ row->chars = realloc(row->chars,row->size+2); memmove(row->chars+at+1,row->chars+at,row->size-at+1); row->size++; } row->chars[at] = c; editorUpdateRow(row); E.dirty++; } /* Append the string 's' at the end of a row */ static void editorRowAppendString(erow *row, char *s, size_t len) { row->chars = realloc(row->chars,row->size+len+1); memcpy(row->chars+row->size,s,len); row->size += len; row->chars[row->size] = '\0'; editorUpdateRow(row); E.dirty++; } /* Delete the character at offset 'at' from the specified row. */ static void editorRowDelChar(erow *row, int at) { if (row->size <= at) return; memmove(row->chars+at,row->chars+at+1,row->size-at); editorUpdateRow(row); row->size--; E.dirty++; } /* Insert the specified char at the current prompt position. */ static void editorInsertChar(int c) { int filerow = E.rowoff+E.cy; int filecol = E.coloff+E.cx; erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; /* If the row where the cursor is currently located does not exist in our * logical representaion of the file, add enough empty rows as needed. */ if (!row) { while(E.numrows <= filerow) editorInsertRow(E.numrows,"",0); } row = &E.row[filerow]; editorRowInsertChar(row,filecol,c); if (E.cx == E.cols-1) E.coloff++; else E.cx++; E.dirty++; } /* Inserting a newline is slightly complex as we have to handle inserting a * newline in the middle of a line, splitting the line as needed. */ static void editorInsertNewline(void) { int filerow = E.rowoff+E.cy; int filecol = E.coloff+E.cx; erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; if (!row) { if (filerow == E.numrows) { editorInsertRow(filerow,"",0); goto fixcursor; } return; } /* If the cursor is over the current line size, we want to conceptually * think it's just over the last character. */ if (filecol >= row->size) filecol = row->size; if (filecol == 0) { editorInsertRow(filerow,"",0); } else { /* We are in the middle of a line. Split it between two rows. */ editorInsertRow(filerow+1,row->chars+filecol,row->size-filecol); row = &E.row[filerow]; row->chars[filecol] = '\0'; row->size = filecol; editorUpdateRow(row); } fixcursor: if (E.startrow + E.cy == E.endrow-1) { E.rowoff++; } else { E.cy++; } E.cx = 0; E.coloff = 0; } /* Delete the char at the current prompt position. */ static void editorDelChar() { int filerow = E.rowoff+E.cy; int filecol = E.coloff+E.cx; erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; if (!row || (filecol == 0 && filerow == 0)) return; if (filecol == 0) { /* Handle the case of column 0, we need to move the current line * on the right of the previous one. */ filecol = E.row[filerow-1].size; editorRowAppendString(&E.row[filerow-1],row->chars,row->size); editorDelRow(filerow); row = NULL; if (E.cy == 0) E.rowoff--; else E.cy--; E.cx = filecol; if (E.cx >= E.cols) { int shift = (E.cols-E.cx)+1; E.cx -= shift; E.coloff += shift; } } else { editorRowDelChar(row,filecol-1); if (E.cx == 0 && E.coloff) E.coloff--; else E.cx--; } if (row) editorUpdateRow(row); E.dirty++; } static void editorUncommentCursorRow() { erow *row = &E.row[E.rowoff+E.cy]; E.coloff = 0; E.cx = 4; editorUpdateRow(row); editorDelChar(); editorDelChar(); editorDelChar(); editorDelChar(); } static void editorCommentCursorRow() { erow *row = &E.row[E.rowoff+E.cy]; editorRowInsertChar(row, 0, ' '); editorRowInsertChar(row, 0, '?'); editorRowInsertChar(row, 0, '-'); editorRowInsertChar(row, 0, '-'); E.coloff = 0; E.cx = 0; } /* Load the specified program in the editor memory and returns 0 on success * or 1 on error. */ int editorOpen(char *filename) { FILE *fp; E.dirty = 0; free(E.filename); size_t fnlen = strlen(filename)+1; E.filename = malloc(fnlen); memcpy(E.filename,filename,fnlen); fp = fopen(filename,"r"); if (!fp) { if (errno != ENOENT) { perror("Opening file"); exit(1); } return 1; } char *line = NULL; size_t linecap = 0; ssize_t linelen; while((linelen = getline(&line,&linecap,fp)) != -1) { if (linelen && (line[linelen-1] == '\n' || line[linelen-1] == '\r')) line[--linelen] = '\0'; editorInsertRow(E.numrows,line,linelen); } free(line); fclose(fp); E.dirty = 0; return 0; } /* Save the current file on disk. Return 0 on success, 1 on error. */ static int editorSaveToDisk(void) { int len; char *buf = editorRowsToString(&len); int fd = open(E.filename,O_RDWR|O_CREAT,0644); if (fd == -1) goto writeerr; /* Use truncate + a single write(2) call in order to make saving * a bit safer, under the limits of what we can do in a small editor. */ if (ftruncate(fd,len) == -1) goto writeerr; if (write(fd,buf,len) != len) goto writeerr; close(fd); free(buf); E.dirty = 0; return 0; writeerr: free(buf); if (fd != -1) close(fd); /* TODO: better error handling. */ /* I haven't gotten to this yet since we have version control. */ endwin(); printf("Can't save! I/O error: %s",strerror(errno)); exit(1); return 1; } /* ============================= Terminal update ============================ */ static void editorMenu(void) { attrset(A_REVERSE); for (int x = 0; x < COLS; ++x) mvaddch(LINES-1, x, ' '); attrset(A_NORMAL); menu_column = 2; draw_menu_item("^x", "run"); if (Previous_error != NULL) { attron(A_BOLD); draw_menu_item("^c", "abort"); attroff(A_BOLD); } draw_menu_item("^g", "go"); draw_menu_item("^b", "big picture"); draw_menu_item("^f", "find"); draw_menu_item("^h", "backspace"); draw_menu_item("^l", "end of line"); /* draw_menu_item("^/|^-|^_", "(un)comment line"); */ attroff(A_REVERSE); mvaddstr(LINES-1, menu_column, " ^/"); attron(COLOR_PAIR(COLOR_PAIR_MENU_ALTERNATE)); addstr("|"); attroff(COLOR_PAIR(COLOR_PAIR_MENU_ALTERNATE)); addstr("^-"); attron(COLOR_PAIR(COLOR_PAIR_MENU_ALTERNATE)); addstr("|"); attroff(COLOR_PAIR(COLOR_PAIR_MENU_ALTERNATE)); addstr("^_ "); menu_column += 10; attron(A_REVERSE); mvaddstr(LINES-1, menu_column, " (un)comment line "); menu_column += 18; attrset(A_NORMAL); } static void editorFindMenu(void) { attrset(A_REVERSE); for (int x = 0; x < COLS; ++x) mvaddch(LINES-1, x, ' '); attrset(A_NORMAL); menu_column = 2; draw_menu_item("^x", "cancel"); draw_menu_item("Enter", "submit"); draw_menu_item("^h", "backspace"); draw_menu_item("^u", "clear"); /* draw_menu_item("←|↑", "previous"); */ attroff(A_REVERSE); mvaddstr(LINES-1, menu_column, " ←|↑ "); menu_column += 5; /* strlen isn't sufficient */ attron(A_REVERSE); draw_string_on_menu("previous"); /* draw_menu_item("↓|→", "next"); */ attroff(A_REVERSE); mvaddstr(LINES-1, menu_column, " ↓|→ "); menu_column += 5; /* strlen isn't sufficient */ attron(A_REVERSE); draw_string_on_menu("next"); attrset(A_NORMAL); } static void editorGoMenu(void) { attrset(A_REVERSE); for (int x = 0; x < COLS; ++x) mvaddch(LINES-1, x, ' '); attrset(A_NORMAL); menu_column = 2; draw_menu_item("^x", "cancel"); draw_menu_item("Enter", "submit"); draw_menu_item("^h", "backspace"); draw_menu_item("^u", "clear"); attrset(A_NORMAL); } static void editorRefreshScreen(void (*menu_func)(void)) { int y; erow *r; int current_color = -1; curs_set(0); mvaddstr(E.startrow, 0, ""); clrtobot(); attrset(A_NORMAL); /* Draw all line numbers first so they don't mess up curses state later * when rendering lines. */ for (y = E.startrow; y < E.endrow; y++) { int filerow = E.rowoff+y-E.startrow; if (filerow >= E.numrows) { continue; } mvaddstr(y, 0, ""); attron(COLOR_PAIR(COLOR_PAIR_FADE)); printw("%3d ", filerow+1); // %3d = LINE_NUMBER_SPACE-1 attroff(COLOR_PAIR(COLOR_PAIR_FADE)); } for (y = E.startrow; y < E.endrow; y++) { int filerow = E.rowoff+y-E.startrow; if (filerow >= E.numrows) { continue; } r = &E.row[filerow]; int len = r->rsize - E.coloff; mvaddstr(y, E.startcol, ""); if (len > 0) { if (len > E.cols) len = E.cols; char *c = r->render+E.coloff; unsigned char *hl = r->hl+E.coloff; int j; for (j = 0; j < len; j++) { if (hl[j] == HL_NONPRINT) { char sym; attron(A_REVERSE); if (c[j] <= 26) sym = '@'+c[j]; else sym = '?'; addch(sym); attroff(A_REVERSE); } else if (hl[j] == HL_NORMAL) { if (current_color != -1) { attrset(A_NORMAL); current_color = -1; } addch(c[j]); } else { int color = editorSyntaxToColorPair(hl[j]); if (color != current_color) { attrset(COLOR_PAIR(color)); current_color = color; } addch(c[j]); } } } } render_previous_error(); (*menu_func)(); /* Put cursor at its current position. Note that the horizontal position * at which the cursor is displayed may be different compared to 'E.cx' * because of TABs. */ int j; int cx = 0; int filerow = E.rowoff+E.cy; erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; if (row) { for (j = E.coloff; j < (E.cx+E.coloff); j++) { if (j < row->size && row->chars[j] == TAB) cx += 7-((cx)%8); cx++; } } mvaddstr(E.cy+E.startrow, cx+LINE_NUMBER_SPACE, ""); curs_set(1); } /* =============================== Find mode ================================ */ #define KILO_QUERY_LEN 256 static void editorFind() { char query[KILO_QUERY_LEN+1] = {0}; int qlen = 0; int last_match = -1; /* Last line where a match was found. -1 for none. */ int find_next = 0; /* if 1 search next, if -1 search prev. */ int saved_hl_line = -1; /* No saved HL */ char *saved_hl = NULL; #define FIND_RESTORE_HL do { \ if (saved_hl) { \ memcpy(E.row[saved_hl_line].hl,saved_hl, E.row[saved_hl_line].rsize); \ free(saved_hl); \ saved_hl = NULL; \ } \ } while (0) /* Save the cursor position in order to restore it later. */ int saved_cx = E.cx, saved_cy = E.cy; int saved_coloff = E.coloff, saved_rowoff = E.rowoff; while(1) { editorRefreshScreen(editorFindMenu); mvprintw(LINES-2, 0, "Find: %s", query); int c = getch(); if (c == KEY_BACKSPACE || c == DELETE || c == CTRL_H) { if (qlen != 0) query[--qlen] = '\0'; last_match = -1; } else if (c == CTRL_X || c == ENTER) { if (c == CTRL_X) { E.cx = saved_cx; E.cy = saved_cy; E.coloff = saved_coloff; E.rowoff = saved_rowoff; } FIND_RESTORE_HL; return; } else if (c == CTRL_U) { qlen = 0; query[qlen] = '\0'; } else if (c == KEY_RIGHT || c == KEY_DOWN) { find_next = 1; } else if (c == KEY_LEFT || c == KEY_UP) { find_next = -1; } else if (isprint(c)) { if (qlen < KILO_QUERY_LEN) { query[qlen++] = c; query[qlen] = '\0'; last_match = -1; } } /* Search occurrence. */ if (last_match == -1) find_next = 1; if (find_next) { char *match = NULL; int match_offset = 0; int i, current = last_match; for (i = 0; i < E.numrows; i++) { current += find_next; if (current == -1) current = E.numrows-1; else if (current == E.numrows) current = 0; match = strstr(E.row[current].render,query); if (match) { match_offset = match-E.row[current].render; break; } } find_next = 0; /* Highlight */ FIND_RESTORE_HL; if (match) { erow *row = &E.row[current]; last_match = current; if (row->hl) { saved_hl_line = current; saved_hl = malloc(row->rsize); memcpy(saved_hl,row->hl,row->rsize); memset(row->hl+match_offset,HL_MATCH,qlen); } E.cy = 0; E.cx = match_offset; E.rowoff = current; E.coloff = 0; /* Scroll horizontally as needed. */ if (E.cx > E.cols) { int diff = E.cx - E.cols; E.cx -= diff; E.coloff += diff; } } } } } /* ========================= Editor events handling ======================== */ static int editorAtStartOfLine() { return E.coloff == 0 && E.cx == 0; } /* Handle cursor position change because arrow keys were pressed. */ static void editorMoveCursor(int key) { int filerow = E.rowoff+E.cy; int filecol = E.coloff+E.cx; int rowlen; erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; switch(key) { case KEY_LEFT: if (E.cx == 0) { if (E.coloff) { E.coloff--; } else { if (filerow > 0) { E.cy--; E.cx = E.row[filerow-1].size; if (E.cx > E.cols-1) { E.coloff = E.cx-E.cols+1; E.cx = E.cols-1; } } } } else { E.cx -= 1; } break; case KEY_RIGHT: if (row && filecol < row->size) { if (E.cx == E.cols-1) { E.coloff++; } else { E.cx += 1; } } else if (row && filecol == row->size) { E.cx = 0; E.coloff = 0; if (E.startrow + E.cy == E.endrow-1) { E.rowoff++; } else { E.cy += 1; } } break; case KEY_UP: if (E.cy == 0) { if (E.rowoff) E.rowoff--; } else { E.cy -= 1; } break; case KEY_DOWN: if (filerow < E.numrows-1) { if (E.startrow + E.cy == E.endrow-1) { E.rowoff++; } else { E.cy += 1; } } break; } /* Fix cx if the current line has not enough chars. */ filerow = E.rowoff+E.cy; filecol = E.coloff+E.cx; row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; rowlen = row ? row->size : 0; if (filecol > rowlen) { E.cx -= filecol-rowlen; if (E.cx < 0) { E.coloff += E.cx; E.cx = 0; } } } static int identifier_char(char c) { /* keep sync'd with llex, with one exception for prose */ return isalnum(c) || c == '_' || c == ':'; } static void word_at_cursor(char* out, int capacity) { erow* row = &E.row[E.rowoff + E.cy]; int cidx = E.coloff + E.cx; int len = 0; memset(out, 0, capacity); if (row == NULL) return; /* scan back to first identifier char */ while (cidx > 0) { --cidx; if (!identifier_char(row->chars[cidx])) { ++cidx; break; } } /* now scan forward */ for (len = 0; cidx+len < row->size; ++len) { if (!identifier_char(row->chars[cidx+len])) break; } if (len < capacity) strncpy(out, &row->chars[cidx], len); } /* Jump to some definition. */ extern char Current_definition[]; #define CURRENT_DEFINITION_LEN 256 static void editorGo(lua_State* L) { char query[CURRENT_DEFINITION_LEN+1] = {0}; int qlen = 0; if (strlen(Current_definition) > 0) { /* We're currently editing a definition. Save it. */ editorSaveToDisk(); load_editor_buffer_to_current_definition_in_image_and_reload(L); } word_at_cursor(query, CURRENT_DEFINITION_LEN); qlen = strlen(query); while(1) { editorRefreshScreen(editorGoMenu); mvaddstr(E.endrow-1, 0, ""); clrtoeol(); mvprintw(E.endrow-1, 0, "Go to: %s", query); int c = getch(); if (c == KEY_BACKSPACE || c == DELETE || c == CTRL_H) { if (qlen != 0) query[--qlen] = '\0'; } else if (c == CTRL_X || c == ENTER) { if (c == ENTER) { save_to_current_definition_and_editor_buffer(L, query); clearEditor(); if (starts_with(query, "doc:")) E.syntax = &ProseSyntax; else E.syntax = &LuaSyntax; editorOpen("teliva_editor_buffer"); attrset(A_NORMAL); clear(); draw_current_definition_name_and_callers(L); } return; } else if (c == CTRL_U) { qlen = 0; query[qlen] = '\0'; } else if (isprint(c)) { if (qlen < CURRENT_DEFINITION_LEN) { query[qlen++] = c; query[qlen] = '\0'; } } } } /* Process events arriving from the standard input, which is, the user * is typing stuff on the terminal. */ static int Quit = 0; static int Back_to_big_picture = 0; static void editorProcessKeypress2(int c) { switch(c) { case ENTER: { editorInsertNewline(); /* auto-indent */ erow* prevrow = &E.row[E.rowoff + E.cy - 1]; for (int x = 0; x < prevrow->size && prevrow->chars[x] == ' '; ++x) editorInsertChar(' '); } break; case CTRL_C: if (Previous_error != NULL) exit(1); break; case CTRL_X: /* Save and quit. */ editorSaveToDisk(); save_editor_state(E.rowoff, E.coloff, E.cy, E.cx); Quit = 1; break; case CTRL_F: editorFind(); break; case KEY_BACKSPACE: case DELETE: case CTRL_H: editorDelChar(); break; case KEY_NPAGE: case KEY_PPAGE: if (c == KEY_PPAGE && E.cy != 0) E.cy = 0; else if (c == KEY_NPAGE && E.startrow + E.cy != E.endrow-1) E.cy = E.endrow-1-E.startrow; { int times = E.endrow-E.startrow; while(times--) editorMoveCursor(c == KEY_PPAGE ? KEY_UP : KEY_DOWN); } break; case CTRL_A: while (!editorAtStartOfLine()) editorMoveCursor(KEY_LEFT); break; case CTRL_L: while (1) { editorMoveCursor(KEY_RIGHT); if (editorAtStartOfLine()) { editorMoveCursor(KEY_LEFT); break; } } break; case CTRL_U: while (!editorAtStartOfLine()) editorDelChar(); break; case CTRL_K: while (1) { editorMoveCursor(KEY_RIGHT); if (editorAtStartOfLine()) { editorMoveCursor(KEY_LEFT); break; } editorDelChar(); } break; case CTRL_SLASH: /* same as CTRL_UNDERSCORE */ if (starts_with(E.row[E.rowoff+E.cy].chars, "--? ")) editorUncommentCursorRow(); else editorCommentCursorRow(); break; case KEY_UP: case KEY_DOWN: case KEY_LEFT: case KEY_RIGHT: editorMoveCursor(c); break; case TAB: /* insert 2 spaces */ editorInsertChar(' '); editorInsertChar(' '); break; default: if (c >= ' ') editorInsertChar(c); break; } } static void editorProcessKeypress(lua_State* L) { int c = getch(); //? mvprintw(LINES-3, 60, "key: %d\n", c); //? getch(); switch(c) { case CTRL_G: /* Go to a different definition. */ editorGo(L); break; case CTRL_B: /* Go to big-picture view. */ editorSaveToDisk(); Quit = 1; Back_to_big_picture = 1; break; default: editorProcessKeypress2(c); } } static void initEditor(void) { E.cx = 0; E.cy = 0; E.rowoff = 0; E.coloff = 0; E.numrows = 0; E.row = NULL; E.dirty = 0; E.filename = NULL; } /* return true if user chose to back into the big picture view */ int edit(lua_State* L, char* filename, char* definition_name) { Quit = 0; Back_to_big_picture = 0; initEditor(); if (starts_with(definition_name, "doc:")) E.syntax = &ProseSyntax; else E.syntax = &LuaSyntax; editorOpen(filename); attrset(A_NORMAL); clear(); draw_current_definition_name_and_callers(L); while(!Quit) { /* update on resize */ E.startcol = LINE_NUMBER_SPACE; E.cols = COLS-LINE_NUMBER_SPACE; E.startrow = CALLERS_SPACE; E.endrow = LINES-MENU_SPACE; editorRefreshScreen(editorMenu); editorProcessKeypress(L); } return Back_to_big_picture; } /* Like editFrom(), but no highlighting, no callers. */ int editProse(lua_State* L, char* filename) { Quit = 0; Back_to_big_picture = 0; initEditor(); E.syntax = &ProseSyntax; editorOpen(filename); attrset(A_NORMAL); clear(); while(!Quit) { /* update on resize */ E.startcol = LINE_NUMBER_SPACE; E.cols = COLS-LINE_NUMBER_SPACE; E.startrow = CALLERS_SPACE; E.endrow = LINES-MENU_SPACE; editorRefreshScreen(editorMenu); editorProcessKeypress(L); } return Back_to_big_picture; } static void editorNonCodeMenu(void) { attrset(A_REVERSE); for (int x = 0; x < COLS; ++x) mvaddch(LINES-1, x, ' '); attrset(A_NORMAL); menu_column = 2; draw_menu_item("^x", "back"); if (Previous_error != NULL) { attron(A_BOLD); draw_menu_item("^c", "abort"); attroff(A_BOLD); } draw_menu_item("^f", "find"); draw_menu_item("^h", "backspace"); draw_menu_item("^l", "end of line"); /* draw_menu_item("^/|^-|^_", "(un)comment line"); */ attroff(A_REVERSE); mvaddstr(LINES-1, menu_column, " ^/"); attron(COLOR_PAIR(COLOR_PAIR_MENU_ALTERNATE)); addstr("|"); attroff(COLOR_PAIR(COLOR_PAIR_MENU_ALTERNATE)); addstr("^-"); attron(COLOR_PAIR(COLOR_PAIR_MENU_ALTERNATE)); addstr("|"); attroff(COLOR_PAIR(COLOR_PAIR_MENU_ALTERNATE)); addstr("^_ "); menu_column += 10; attron(A_REVERSE); mvaddstr(LINES-1, menu_column, " (un)comment line "); menu_column += 18; attrset(A_NORMAL); } void editNonCode(char* filename) { Quit = 0; Back_to_big_picture = 0; initEditor(); editorOpen(filename); while(!Quit) { /* update on resize */ E.startcol = LINE_NUMBER_SPACE; E.cols = COLS-LINE_NUMBER_SPACE; E.startrow = CALLERS_SPACE; E.endrow = LINES-MENU_SPACE; editorRefreshScreen(editorNonCodeMenu); int c = getch(); editorProcessKeypress2(c); } } #define MIN(x, y) ((x) < (y) ? (x) : (y)) void print_file_permission_suggestions(int row); void editFilePermissions(char* filename) { Quit = 0; Back_to_big_picture = 0; initEditor(); E.syntax = &LuaSyntax; E.startcol = LINE_NUMBER_SPACE; E.startrow = 1; /* space for function header */ E.endrow = 10; /* nudge people to keep function short */ editorOpen(filename); while(!Quit) { /* update on resize */ E.cols = COLS-LINE_NUMBER_SPACE; editorRefreshScreen(editorNonCodeMenu); int y, x; getyx(stdscr, y, x); mvaddstr(0, 0, "function file_operation_permitted(filename, is_write)"); int past_end_row = MIN(E.startrow + E.numrows, E.endrow); mvaddstr(past_end_row, 0, "end"); attrset(COLOR_PAIR(COLOR_PAIR_LUA_COMMENT)); print_file_permission_suggestions(past_end_row+2); attrset(A_NORMAL); mvaddstr(y, x, ""); int c = getch(); editorProcessKeypress2(c); } } /* return true if user chose to back into the big picture view */ int editFrom(lua_State* L, char* filename, char* definition_name, int rowoff, int coloff, int cy, int cx) { Quit = 0; Back_to_big_picture = 0; initEditor(); if (starts_with(definition_name, "doc:")) E.syntax = &ProseSyntax; else E.syntax = &LuaSyntax; E.rowoff = rowoff; E.coloff = coloff; E.cy = cy; E.cx = cx; editorOpen(filename); attrset(A_NORMAL); clear(); draw_current_definition_name_and_callers(L); while(!Quit) { /* update on resize */ E.startcol = LINE_NUMBER_SPACE; E.cols = COLS-LINE_NUMBER_SPACE; E.startrow = CALLERS_SPACE; E.endrow = LINES-MENU_SPACE; editorRefreshScreen(editorMenu); editorProcessKeypress(L); } return Back_to_big_picture; } /* Like editFrom(), but no highlighting, no callers. */ int editProseFrom(lua_State* L, char* filename, int rowoff, int coloff, int cy, int cx) { Quit = 0; Back_to_big_picture = 0; initEditor(); E.syntax = &ProseSyntax; E.rowoff = rowoff; E.coloff = coloff; E.cy = cy; E.cx = cx; editorOpen(filename); attrset(A_NORMAL); clear(); while(!Quit) { /* update on resize */ E.startcol = LINE_NUMBER_SPACE; E.cols = COLS-LINE_NUMBER_SPACE; E.startrow = CALLERS_SPACE; E.endrow = LINES-MENU_SPACE; editorRefreshScreen(editorMenu); editorProcessKeypress(L); } return Back_to_big_picture; } int resumeEdit(lua_State* L) { Quit = 0; Back_to_big_picture = 0; attrset(A_NORMAL); clear(); draw_current_definition_name_and_callers(L); while(!Quit) { /* update on resize */ E.startcol = LINE_NUMBER_SPACE; E.cols = COLS-LINE_NUMBER_SPACE; E.startrow = CALLERS_SPACE; E.endrow = LINES-MENU_SPACE; editorRefreshScreen(editorMenu); editorProcessKeypress(L); } return Back_to_big_picture; } void resumeNonCodeEdit() { Quit = 0; Back_to_big_picture = 0; while(!Quit) { /* update on resize */ E.startcol = LINE_NUMBER_SPACE; E.cols = COLS-LINE_NUMBER_SPACE; E.startrow = CALLERS_SPACE; E.endrow = LINES-MENU_SPACE; editorRefreshScreen(editorNonCodeMenu); int c = getch(); editorProcessKeypress2(c); } } void resumeFilePermissionsEdit() { Quit = 0; Back_to_big_picture = 0; E.startcol = LINE_NUMBER_SPACE; E.startrow = 1; /* space for function header */ E.endrow = 10; /* nudge people to keep function short */ while(!Quit) { /* update on resize */ E.cols = COLS-LINE_NUMBER_SPACE; editorRefreshScreen(editorNonCodeMenu); int y, x; getyx(stdscr, y, x); mvaddstr(0, 0, "function file_operation_permitted(filename, is_write)"); mvaddstr(MIN(E.startrow + E.numrows, E.endrow), 0, "end"); mvaddstr(y, x, ""); int c = getch(); editorProcessKeypress2(c); } } /* vim:tabstop=4:shiftwidth=0:expandtab:softtabstop=-1 */ ================================================ FILE: src/lapi.c ================================================ /* ** $Id: lapi.c,v 2.55.1.5 2008/07/04 18:41:18 roberto Exp $ ** Lua API ** See Copyright Notice in lua.h */ #include #include #include #include #define lapi_c #define LUA_CORE #include "lua.h" #include "lapi.h" #include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "lgc.h" #include "lmem.h" #include "lobject.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "ltm.h" #include "lundump.h" #include "lvm.h" const char lua_ident[] = "$Lua: " LUA_RELEASE " " LUA_COPYRIGHT " $\n" "$Authors: " LUA_AUTHORS " $\n" "$URL: www.lua.org $\n"; #define api_checknelems(L, n) api_check(L, (n) <= (L->top - L->base)) #define api_checkvalidindex(L, i) api_check(L, (i) != luaO_nilobject) #define api_incr_top(L) {api_check(L, L->top < L->ci->top); L->top++;} static TValue *index2adr (lua_State *L, int idx) { if (idx > 0) { TValue *o = L->base + (idx - 1); api_check(L, idx <= L->ci->top - L->base); if (o >= L->top) return cast(TValue *, luaO_nilobject); else return o; } else if (idx > LUA_REGISTRYINDEX) { api_check(L, idx != 0 && -idx <= L->top - L->base); return L->top + idx; } else switch (idx) { /* pseudo-indices */ case LUA_REGISTRYINDEX: return registry(L); case LUA_ENVIRONINDEX: { Closure *func = curr_func(L); sethvalue(L, &L->env, func->c.env); return &L->env; } case LUA_GLOBALSINDEX: return gt(L); default: { Closure *func = curr_func(L); idx = LUA_GLOBALSINDEX - idx; return (idx <= func->c.nupvalues) ? &func->c.upvalue[idx-1] : cast(TValue *, luaO_nilobject); } } } static Table *getcurrenv (lua_State *L) { if (L->ci == L->base_ci) /* no enclosing function? */ return hvalue(gt(L)); /* use global table as environment */ else { Closure *func = curr_func(L); return func->c.env; } } void luaA_pushobject (lua_State *L, const TValue *o) { setobj2s(L, L->top, o); api_incr_top(L); } LUA_API int lua_checkstack (lua_State *L, int size) { int res = 1; lua_lock(L); if (size > LUAI_MAXCSTACK || (L->top - L->base + size) > LUAI_MAXCSTACK) res = 0; /* stack overflow */ else if (size > 0) { luaD_checkstack(L, size); if (L->ci->top < L->top + size) L->ci->top = L->top + size; } lua_unlock(L); return res; } LUA_API void lua_xmove (lua_State *from, lua_State *to, int n) { int i; if (from == to) return; lua_lock(to); api_checknelems(from, n); api_check(from, G(from) == G(to)); api_check(from, to->ci->top - to->top >= n); from->top -= n; for (i = 0; i < n; i++) { setobj2s(to, to->top++, from->top + i); } lua_unlock(to); } LUA_API void lua_setlevel (lua_State *from, lua_State *to) { to->nCcalls = from->nCcalls; } LUA_API lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf) { lua_CFunction old; lua_lock(L); old = G(L)->panic; G(L)->panic = panicf; lua_unlock(L); return old; } LUA_API lua_State *lua_newthread (lua_State *L) { lua_State *L1; lua_lock(L); luaC_checkGC(L); L1 = luaE_newthread(L); setthvalue(L, L->top, L1); api_incr_top(L); lua_unlock(L); return L1; } /* ** basic stack manipulation */ LUA_API int lua_gettop (lua_State *L) { return cast_int(L->top - L->base); } LUA_API void lua_settop (lua_State *L, int idx) { lua_lock(L); if (idx >= 0) { api_check(L, idx <= L->stack_last - L->base); while (L->top < L->base + idx) setnilvalue(L->top++); L->top = L->base + idx; } else { api_check(L, -(idx+1) <= (L->top - L->base)); L->top += idx+1; /* `subtract' index (index is negative) */ } lua_unlock(L); } LUA_API void lua_remove (lua_State *L, int idx) { StkId p; lua_lock(L); p = index2adr(L, idx); api_checkvalidindex(L, p); while (++p < L->top) setobjs2s(L, p-1, p); L->top--; lua_unlock(L); } LUA_API void lua_insert (lua_State *L, int idx) { StkId p; StkId q; lua_lock(L); p = index2adr(L, idx); api_checkvalidindex(L, p); for (q = L->top; q>p; q--) setobjs2s(L, q, q-1); setobjs2s(L, p, L->top); lua_unlock(L); } LUA_API void lua_replace (lua_State *L, int idx) { StkId o; lua_lock(L); /* explicit test for incompatible code */ if (idx == LUA_ENVIRONINDEX && L->ci == L->base_ci) luaG_runerror(L, "no calling environment"); api_checknelems(L, 1); o = index2adr(L, idx); api_checkvalidindex(L, o); if (idx == LUA_ENVIRONINDEX) { Closure *func = curr_func(L); api_check(L, ttistable(L->top - 1)); func->c.env = hvalue(L->top - 1); luaC_barrier(L, func, L->top - 1); } else { setobj(L, o, L->top - 1); if (idx < LUA_GLOBALSINDEX) /* function upvalue? */ luaC_barrier(L, curr_func(L), L->top - 1); } L->top--; lua_unlock(L); } LUA_API void lua_pushvalue (lua_State *L, int idx) { lua_lock(L); setobj2s(L, L->top, index2adr(L, idx)); api_incr_top(L); lua_unlock(L); } /* ** access functions (stack -> C) */ LUA_API int lua_type (lua_State *L, int idx) { StkId o = index2adr(L, idx); return (o == luaO_nilobject) ? LUA_TNONE : ttype(o); } LUA_API const char *lua_typename (lua_State *L, int t) { UNUSED(L); return (t == LUA_TNONE) ? "no value" : luaT_typenames[t]; } LUA_API int lua_iscfunction (lua_State *L, int idx) { StkId o = index2adr(L, idx); return iscfunction(o); } LUA_API int lua_isnumber (lua_State *L, int idx) { TValue n; const TValue *o = index2adr(L, idx); return tonumber(o, &n); } LUA_API int lua_isstring (lua_State *L, int idx) { int t = lua_type(L, idx); return (t == LUA_TSTRING || t == LUA_TNUMBER); } LUA_API int lua_isuserdata (lua_State *L, int idx) { const TValue *o = index2adr(L, idx); return (ttisuserdata(o) || ttislightuserdata(o)); } LUA_API int lua_rawequal (lua_State *L, int index1, int index2) { StkId o1 = index2adr(L, index1); StkId o2 = index2adr(L, index2); return (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 : luaO_rawequalObj(o1, o2); } LUA_API int lua_equal (lua_State *L, int index1, int index2) { StkId o1, o2; int i; lua_lock(L); /* may call tag method */ o1 = index2adr(L, index1); o2 = index2adr(L, index2); i = (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 : equalobj(L, o1, o2); lua_unlock(L); return i; } LUA_API int lua_lessthan (lua_State *L, int index1, int index2) { StkId o1, o2; int i; lua_lock(L); /* may call tag method */ o1 = index2adr(L, index1); o2 = index2adr(L, index2); i = (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 : luaV_lessthan(L, o1, o2); lua_unlock(L); return i; } LUA_API lua_Number lua_tonumber (lua_State *L, int idx) { TValue n; const TValue *o = index2adr(L, idx); if (tonumber(o, &n)) return nvalue(o); else return 0; } LUA_API lua_Integer lua_tointeger (lua_State *L, int idx) { TValue n; const TValue *o = index2adr(L, idx); if (tonumber(o, &n)) { lua_Integer res; lua_Number num = nvalue(o); lua_number2integer(res, num); return res; } else return 0; } LUA_API int lua_toboolean (lua_State *L, int idx) { const TValue *o = index2adr(L, idx); return !l_isfalse(o); } LUA_API const char *lua_tolstring (lua_State *L, int idx, size_t *len) { StkId o = index2adr(L, idx); if (!ttisstring(o)) { lua_lock(L); /* `luaV_tostring' may create a new string */ if (!luaV_tostring(L, o)) { /* conversion failed? */ if (len != NULL) *len = 0; lua_unlock(L); return NULL; } luaC_checkGC(L); o = index2adr(L, idx); /* previous call may reallocate the stack */ lua_unlock(L); } if (len != NULL) *len = tsvalue(o)->len; return svalue(o); } LUA_API size_t lua_objlen (lua_State *L, int idx) { StkId o = index2adr(L, idx); switch (ttype(o)) { case LUA_TSTRING: return tsvalue(o)->len; case LUA_TUSERDATA: return uvalue(o)->len; case LUA_TTABLE: return luaH_getn(hvalue(o)); case LUA_TNUMBER: { size_t l; lua_lock(L); /* `luaV_tostring' may create a new string */ l = (luaV_tostring(L, o) ? tsvalue(o)->len : 0); lua_unlock(L); return l; } default: return 0; } } LUA_API lua_CFunction lua_tocfunction (lua_State *L, int idx) { StkId o = index2adr(L, idx); return (!iscfunction(o)) ? NULL : clvalue(o)->c.f; } LUA_API void *lua_touserdata (lua_State *L, int idx) { StkId o = index2adr(L, idx); switch (ttype(o)) { case LUA_TUSERDATA: return (rawuvalue(o) + 1); case LUA_TLIGHTUSERDATA: return pvalue(o); default: return NULL; } } LUA_API lua_State *lua_tothread (lua_State *L, int idx) { StkId o = index2adr(L, idx); return (!ttisthread(o)) ? NULL : thvalue(o); } LUA_API const void *lua_topointer (lua_State *L, int idx) { StkId o = index2adr(L, idx); switch (ttype(o)) { case LUA_TTABLE: return hvalue(o); case LUA_TFUNCTION: return clvalue(o); case LUA_TTHREAD: return thvalue(o); case LUA_TUSERDATA: case LUA_TLIGHTUSERDATA: return lua_touserdata(L, idx); default: return NULL; } } /* ** push functions (C -> stack) */ LUA_API void lua_pushnil (lua_State *L) { lua_lock(L); setnilvalue(L->top); api_incr_top(L); lua_unlock(L); } LUA_API void lua_pushnumber (lua_State *L, lua_Number n) { lua_lock(L); setnvalue(L->top, n); api_incr_top(L); lua_unlock(L); } LUA_API void lua_pushinteger (lua_State *L, lua_Integer n) { lua_lock(L); setnvalue(L->top, cast_num(n)); api_incr_top(L); lua_unlock(L); } LUA_API void lua_pushlstring (lua_State *L, const char *s, size_t len) { lua_lock(L); luaC_checkGC(L); setsvalue2s(L, L->top, luaS_newlstr(L, s, len)); api_incr_top(L); lua_unlock(L); } LUA_API void lua_pushstring (lua_State *L, const char *s) { if (s == NULL) lua_pushnil(L); else lua_pushlstring(L, s, strlen(s)); } LUA_API const char *lua_pushvfstring (lua_State *L, const char *fmt, va_list argp) { const char *ret; lua_lock(L); luaC_checkGC(L); ret = luaO_pushvfstring(L, fmt, argp); lua_unlock(L); return ret; } LUA_API const char *lua_pushfstring (lua_State *L, const char *fmt, ...) { const char *ret; va_list argp; lua_lock(L); luaC_checkGC(L); va_start(argp, fmt); ret = luaO_pushvfstring(L, fmt, argp); va_end(argp); lua_unlock(L); return ret; } LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) { Closure *cl; lua_lock(L); luaC_checkGC(L); api_checknelems(L, n); cl = luaF_newCclosure(L, n, getcurrenv(L)); cl->c.f = fn; L->top -= n; while (n--) setobj2n(L, &cl->c.upvalue[n], L->top+n); setclvalue(L, L->top, cl); lua_assert(iswhite(obj2gco(cl))); api_incr_top(L); lua_unlock(L); } LUA_API void lua_pushboolean (lua_State *L, int b) { lua_lock(L); setbvalue(L->top, (b != 0)); /* ensure that true is 1 */ api_incr_top(L); lua_unlock(L); } LUA_API void lua_pushlightuserdata (lua_State *L, void *p) { lua_lock(L); setpvalue(L->top, p); api_incr_top(L); lua_unlock(L); } LUA_API int lua_pushthread (lua_State *L) { lua_lock(L); setthvalue(L, L->top, L); api_incr_top(L); lua_unlock(L); return (G(L)->mainthread == L); } /* ** get functions (Lua -> stack) */ LUA_API void lua_gettable (lua_State *L, int idx) { StkId t; lua_lock(L); t = index2adr(L, idx); api_checkvalidindex(L, t); luaV_gettable(L, t, L->top - 1, L->top - 1); lua_unlock(L); } LUA_API void lua_getfield (lua_State *L, int idx, const char *k) { StkId t; TValue key; lua_lock(L); t = index2adr(L, idx); api_checkvalidindex(L, t); setsvalue(L, &key, luaS_new(L, k)); luaV_gettable(L, t, &key, L->top); api_incr_top(L); lua_unlock(L); } LUA_API void lua_rawget (lua_State *L, int idx) { StkId t; lua_lock(L); t = index2adr(L, idx); api_check(L, ttistable(t)); setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1)); lua_unlock(L); } LUA_API void lua_rawgeti (lua_State *L, int idx, int n) { StkId o; lua_lock(L); o = index2adr(L, idx); api_check(L, ttistable(o)); setobj2s(L, L->top, luaH_getnum(hvalue(o), n)); api_incr_top(L); lua_unlock(L); } LUA_API void lua_createtable (lua_State *L, int narray, int nrec) { lua_lock(L); luaC_checkGC(L); sethvalue(L, L->top, luaH_new(L, narray, nrec)); api_incr_top(L); lua_unlock(L); } LUA_API int lua_getmetatable (lua_State *L, int objindex) { const TValue *obj; Table *mt = NULL; int res; lua_lock(L); obj = index2adr(L, objindex); switch (ttype(obj)) { case LUA_TTABLE: mt = hvalue(obj)->metatable; break; case LUA_TUSERDATA: mt = uvalue(obj)->metatable; break; default: mt = G(L)->mt[ttype(obj)]; break; } if (mt == NULL) res = 0; else { sethvalue(L, L->top, mt); api_incr_top(L); res = 1; } lua_unlock(L); return res; } LUA_API void lua_getfenv (lua_State *L, int idx) { StkId o; lua_lock(L); o = index2adr(L, idx); api_checkvalidindex(L, o); switch (ttype(o)) { case LUA_TFUNCTION: sethvalue(L, L->top, clvalue(o)->c.env); break; case LUA_TUSERDATA: sethvalue(L, L->top, uvalue(o)->env); break; case LUA_TTHREAD: setobj2s(L, L->top, gt(thvalue(o))); break; default: setnilvalue(L->top); break; } api_incr_top(L); lua_unlock(L); } /* ** set functions (stack -> Lua) */ LUA_API void lua_settable (lua_State *L, int idx) { StkId t; lua_lock(L); api_checknelems(L, 2); t = index2adr(L, idx); api_checkvalidindex(L, t); luaV_settable(L, t, L->top - 2, L->top - 1); L->top -= 2; /* pop index and value */ lua_unlock(L); } LUA_API void lua_setfield (lua_State *L, int idx, const char *k) { StkId t; TValue key; lua_lock(L); api_checknelems(L, 1); t = index2adr(L, idx); api_checkvalidindex(L, t); setsvalue(L, &key, luaS_new(L, k)); luaV_settable(L, t, &key, L->top - 1); L->top--; /* pop value */ lua_unlock(L); } LUA_API void lua_rawset (lua_State *L, int idx) { StkId t; lua_lock(L); api_checknelems(L, 2); t = index2adr(L, idx); api_check(L, ttistable(t)); setobj2t(L, luaH_set(L, hvalue(t), L->top-2), L->top-1); luaC_barriert(L, hvalue(t), L->top-1); L->top -= 2; lua_unlock(L); } LUA_API void lua_rawseti (lua_State *L, int idx, int n) { StkId o; lua_lock(L); api_checknelems(L, 1); o = index2adr(L, idx); api_check(L, ttistable(o)); setobj2t(L, luaH_setnum(L, hvalue(o), n), L->top-1); luaC_barriert(L, hvalue(o), L->top-1); L->top--; lua_unlock(L); } LUA_API int lua_setmetatable (lua_State *L, int objindex) { TValue *obj; Table *mt; lua_lock(L); api_checknelems(L, 1); obj = index2adr(L, objindex); api_checkvalidindex(L, obj); if (ttisnil(L->top - 1)) mt = NULL; else { api_check(L, ttistable(L->top - 1)); mt = hvalue(L->top - 1); } switch (ttype(obj)) { case LUA_TTABLE: { hvalue(obj)->metatable = mt; if (mt) luaC_objbarriert(L, hvalue(obj), mt); break; } case LUA_TUSERDATA: { uvalue(obj)->metatable = mt; if (mt) luaC_objbarrier(L, rawuvalue(obj), mt); break; } default: { G(L)->mt[ttype(obj)] = mt; break; } } L->top--; lua_unlock(L); return 1; } LUA_API int lua_setfenv (lua_State *L, int idx) { StkId o; int res = 1; lua_lock(L); api_checknelems(L, 1); o = index2adr(L, idx); api_checkvalidindex(L, o); api_check(L, ttistable(L->top - 1)); switch (ttype(o)) { case LUA_TFUNCTION: clvalue(o)->c.env = hvalue(L->top - 1); break; case LUA_TUSERDATA: uvalue(o)->env = hvalue(L->top - 1); break; case LUA_TTHREAD: sethvalue(L, gt(thvalue(o)), hvalue(L->top - 1)); break; default: res = 0; break; } if (res) luaC_objbarrier(L, gcvalue(o), hvalue(L->top - 1)); L->top--; lua_unlock(L); return res; } /* ** `load' and `call' functions (run Lua code) */ #define adjustresults(L,nres) \ { if (nres == LUA_MULTRET && L->top >= L->ci->top) L->ci->top = L->top; } #define checkresults(L,na,nr) \ api_check(L, (nr) == LUA_MULTRET || (L->ci->top - L->top >= (nr) - (na))) LUA_API void lua_call (lua_State *L, int nargs, int nresults) { StkId func; lua_lock(L); api_checknelems(L, nargs+1); checkresults(L, nargs, nresults); func = L->top - (nargs+1); luaD_call(L, func, nresults); adjustresults(L, nresults); lua_unlock(L); } /* ** Execute a protected call. */ struct CallS { /* data to `f_call' */ StkId func; int nresults; }; static void f_call (lua_State *L, void *ud) { struct CallS *c = cast(struct CallS *, ud); luaD_call(L, c->func, c->nresults); } LUA_API int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc) { struct CallS c; int status; ptrdiff_t func; lua_lock(L); api_checknelems(L, nargs+1); checkresults(L, nargs, nresults); if (errfunc == 0) func = 0; else { StkId o = index2adr(L, errfunc); api_checkvalidindex(L, o); func = savestack(L, o); } c.func = L->top - (nargs+1); /* function to be called */ c.nresults = nresults; status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); adjustresults(L, nresults); lua_unlock(L); return status; } /* ** Execute a protected C call. */ struct CCallS { /* data to `f_Ccall' */ lua_CFunction func; void *ud; }; static void f_Ccall (lua_State *L, void *ud) { struct CCallS *c = cast(struct CCallS *, ud); Closure *cl; cl = luaF_newCclosure(L, 0, getcurrenv(L)); cl->c.f = c->func; setclvalue(L, L->top, cl); /* push function */ api_incr_top(L); setpvalue(L->top, c->ud); /* push only argument */ api_incr_top(L); luaD_call(L, L->top - 2, 0); } LUA_API int lua_cpcall (lua_State *L, lua_CFunction func, void *ud) { struct CCallS c; int status; lua_lock(L); c.func = func; c.ud = ud; status = luaD_pcall(L, f_Ccall, &c, savestack(L, L->top), 0); lua_unlock(L); return status; } LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, const char *chunkname) { ZIO z; int status; lua_lock(L); if (!chunkname) chunkname = "?"; luaZ_init(L, &z, reader, data); status = luaD_protectedparser(L, &z, chunkname); lua_unlock(L); return status; } LUA_API int lua_dump (lua_State *L, lua_Writer writer, void *data) { int status; TValue *o; lua_lock(L); api_checknelems(L, 1); o = L->top - 1; if (isLfunction(o)) status = luaU_dump(L, clvalue(o)->l.p, writer, data, 0); else status = 1; lua_unlock(L); return status; } LUA_API int lua_status (lua_State *L) { return L->status; } /* ** Garbage-collection function */ LUA_API int lua_gc (lua_State *L, int what, int data) { int res = 0; global_State *g; lua_lock(L); g = G(L); switch (what) { case LUA_GCSTOP: { g->GCthreshold = MAX_LUMEM; break; } case LUA_GCRESTART: { g->GCthreshold = g->totalbytes; break; } case LUA_GCCOLLECT: { luaC_fullgc(L); break; } case LUA_GCCOUNT: { /* GC values are expressed in Kbytes: #bytes/2^10 */ res = cast_int(g->totalbytes >> 10); break; } case LUA_GCCOUNTB: { res = cast_int(g->totalbytes & 0x3ff); break; } case LUA_GCSTEP: { lu_mem a = (cast(lu_mem, data) << 10); if (a <= g->totalbytes) g->GCthreshold = g->totalbytes - a; else g->GCthreshold = 0; while (g->GCthreshold <= g->totalbytes) { luaC_step(L); if (g->gcstate == GCSpause) { /* end of cycle? */ res = 1; /* signal it */ break; } } break; } case LUA_GCSETPAUSE: { res = g->gcpause; g->gcpause = data; break; } case LUA_GCSETSTEPMUL: { res = g->gcstepmul; g->gcstepmul = data; break; } default: res = -1; /* invalid option */ } lua_unlock(L); return res; } /* ** miscellaneous functions */ LUA_API int lua_error (lua_State *L) { lua_lock(L); api_checknelems(L, 1); luaG_errormsg(L); lua_unlock(L); return 0; /* to avoid warnings */ } LUA_API int lua_next (lua_State *L, int idx) { StkId t; int more; lua_lock(L); t = index2adr(L, idx); api_check(L, ttistable(t)); more = luaH_next(L, hvalue(t), L->top - 1); if (more) { api_incr_top(L); } else /* no more elements */ L->top -= 1; /* remove key */ lua_unlock(L); return more; } LUA_API void lua_concat (lua_State *L, int n) { lua_lock(L); api_checknelems(L, n); if (n >= 2) { luaC_checkGC(L); luaV_concat(L, n, cast_int(L->top - L->base) - 1); L->top -= (n-1); } else if (n == 0) { /* push empty string */ setsvalue2s(L, L->top, luaS_newlstr(L, "", 0)); api_incr_top(L); } /* else n == 1; nothing to do */ lua_unlock(L); } LUA_API lua_Alloc lua_getallocf (lua_State *L, void **ud) { lua_Alloc f; lua_lock(L); if (ud) *ud = G(L)->ud; f = G(L)->frealloc; lua_unlock(L); return f; } LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud) { lua_lock(L); G(L)->ud = ud; G(L)->frealloc = f; lua_unlock(L); } LUA_API void *lua_newuserdata (lua_State *L, size_t size) { Udata *u; lua_lock(L); luaC_checkGC(L); u = luaS_newudata(L, size, getcurrenv(L)); setuvalue(L, L->top, u); api_incr_top(L); lua_unlock(L); return u + 1; } static const char *aux_upvalue (StkId fi, int n, TValue **val) { Closure *f; if (!ttisfunction(fi)) return NULL; f = clvalue(fi); if (f->c.isC) { if (!(1 <= n && n <= f->c.nupvalues)) return NULL; *val = &f->c.upvalue[n-1]; return ""; } else { Proto *p = f->l.p; if (!(1 <= n && n <= p->sizeupvalues)) return NULL; *val = f->l.upvals[n-1]->v; return getstr(p->upvalues[n-1]); } } LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n) { const char *name; TValue *val; lua_lock(L); name = aux_upvalue(index2adr(L, funcindex), n, &val); if (name) { setobj2s(L, L->top, val); api_incr_top(L); } lua_unlock(L); return name; } LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n) { const char *name; TValue *val; StkId fi; lua_lock(L); fi = index2adr(L, funcindex); api_checknelems(L, 1); name = aux_upvalue(fi, n, &val); if (name) { L->top--; setobj(L, val, L->top); luaC_barrier(L, clvalue(fi), L->top); } lua_unlock(L); return name; } ================================================ FILE: src/lapi.h ================================================ /* ** $Id: lapi.h,v 2.2.1.1 2007/12/27 13:02:25 roberto Exp $ ** Auxiliary functions from Lua API ** See Copyright Notice in lua.h */ #ifndef lapi_h #define lapi_h #include "lobject.h" LUAI_FUNC void luaA_pushobject (lua_State *L, const TValue *o); #endif ================================================ FILE: src/lauxlib.c ================================================ /* ** $Id: lauxlib.c,v 1.159.1.3 2008/01/21 13:20:51 roberto Exp $ ** Auxiliary functions for building Lua libraries ** See Copyright Notice in lua.h */ #include #include #include #include #include #include /* This file uses only the official API of Lua. ** Any function declared here could be written as an application function. */ #define lauxlib_c #define LUA_LIB #include "lua.h" #include "lauxlib.h" #define FREELIST_REF 0 /* free list of references */ /* convert a stack index to positive */ #define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : \ lua_gettop(L) + (i) + 1) /* ** {====================================================== ** Error-report functions ** ======================================================= */ LUALIB_API int luaL_argerror (lua_State *L, int narg, const char *extramsg) { lua_Debug ar; if (!lua_getstack(L, 0, &ar)) /* no stack frame? */ return luaL_error(L, "bad argument #%d (%s)", narg, extramsg); lua_getinfo(L, "n", &ar); if (strcmp(ar.namewhat, "method") == 0) { narg--; /* do not count `self' */ if (narg == 0) /* error is in the self argument itself? */ return luaL_error(L, "calling " LUA_QS " on bad self (%s)", ar.name, extramsg); } if (ar.name == NULL) ar.name = "?"; return luaL_error(L, "bad argument #%d to " LUA_QS " (%s)", narg, ar.name, extramsg); } LUALIB_API int luaL_typerror (lua_State *L, int narg, const char *tname) { const char *msg = lua_pushfstring(L, "%s expected, got %s", tname, luaL_typename(L, narg)); return luaL_argerror(L, narg, msg); } static void tag_error (lua_State *L, int narg, int tag) { luaL_typerror(L, narg, lua_typename(L, tag)); } LUALIB_API void luaL_where (lua_State *L, int level) { lua_Debug ar; if (lua_getstack(L, level, &ar)) { /* check function at level */ lua_getinfo(L, "Sl", &ar); /* get info about it */ if (ar.currentline > 0) { /* is there info? */ lua_pushfstring(L, "%s:%d: ", ar.short_src, ar.currentline); return; } } lua_pushliteral(L, ""); /* else, no information available... */ } LUALIB_API int luaL_error (lua_State *L, const char *fmt, ...) { va_list argp; va_start(argp, fmt); luaL_where(L, 1); lua_pushvfstring(L, fmt, argp); va_end(argp); lua_concat(L, 2); return lua_error(L); } /* }====================================================== */ LUALIB_API int luaL_checkoption (lua_State *L, int narg, const char *def, const char *const lst[]) { const char *name = (def) ? luaL_optstring(L, narg, def) : luaL_checkstring(L, narg); int i; for (i=0; lst[i]; i++) if (strcmp(lst[i], name) == 0) return i; return luaL_argerror(L, narg, lua_pushfstring(L, "invalid option " LUA_QS, name)); } LUALIB_API int luaL_newmetatable (lua_State *L, const char *tname) { lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get registry.name */ if (!lua_isnil(L, -1)) /* name already in use? */ return 0; /* leave previous value on top, but return 0 */ lua_pop(L, 1); lua_newtable(L); /* create metatable */ lua_pushvalue(L, -1); lua_setfield(L, LUA_REGISTRYINDEX, tname); /* registry.name = metatable */ return 1; } LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) { void *p = lua_touserdata(L, ud); if (p != NULL) { /* value is a userdata? */ if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get correct metatable */ if (lua_rawequal(L, -1, -2)) { /* does it have the correct mt? */ lua_pop(L, 2); /* remove both metatables */ return p; } } } luaL_typerror(L, ud, tname); /* else error */ return NULL; /* to avoid warnings */ } LUALIB_API void luaL_checkstack (lua_State *L, int space, const char *mes) { if (!lua_checkstack(L, space)) luaL_error(L, "stack overflow (%s)", mes); } LUALIB_API void luaL_checktype (lua_State *L, int narg, int t) { if (lua_type(L, narg) != t) tag_error(L, narg, t); } LUALIB_API void luaL_checkany (lua_State *L, int narg) { if (lua_type(L, narg) == LUA_TNONE) luaL_argerror(L, narg, "value expected"); } LUALIB_API const char *luaL_checklstring (lua_State *L, int narg, size_t *len) { const char *s = lua_tolstring(L, narg, len); if (!s) tag_error(L, narg, LUA_TSTRING); return s; } LUALIB_API const char *luaL_optlstring (lua_State *L, int narg, const char *def, size_t *len) { if (lua_isnoneornil(L, narg)) { if (len) *len = (def ? strlen(def) : 0); return def; } else return luaL_checklstring(L, narg, len); } LUALIB_API lua_Number luaL_checknumber (lua_State *L, int narg) { lua_Number d = lua_tonumber(L, narg); if (d == 0 && !lua_isnumber(L, narg)) /* avoid extra test when d is not 0 */ tag_error(L, narg, LUA_TNUMBER); return d; } LUALIB_API lua_Number luaL_optnumber (lua_State *L, int narg, lua_Number def) { return luaL_opt(L, luaL_checknumber, narg, def); } LUALIB_API lua_Integer luaL_checkinteger (lua_State *L, int narg) { lua_Integer d = lua_tointeger(L, narg); if (d == 0 && !lua_isnumber(L, narg)) /* avoid extra test when d is not 0 */ tag_error(L, narg, LUA_TNUMBER); return d; } LUALIB_API lua_Integer luaL_optinteger (lua_State *L, int narg, lua_Integer def) { return luaL_opt(L, luaL_checkinteger, narg, def); } LUALIB_API int luaL_getmetafield (lua_State *L, int obj, const char *event) { if (!lua_getmetatable(L, obj)) /* no metatable? */ return 0; lua_pushstring(L, event); lua_rawget(L, -2); if (lua_isnil(L, -1)) { lua_pop(L, 2); /* remove metatable and metafield */ return 0; } else { lua_remove(L, -2); /* remove only metatable */ return 1; } } LUALIB_API int luaL_callmeta (lua_State *L, int obj, const char *event) { obj = abs_index(L, obj); if (!luaL_getmetafield(L, obj, event)) /* no metafield? */ return 0; lua_pushvalue(L, obj); lua_call(L, 1, 1); return 1; } LUALIB_API void (luaL_register) (lua_State *L, const char *libname, const luaL_Reg *l) { luaI_openlib(L, libname, l, 0); } static int libsize (const luaL_Reg *l) { int size = 0; for (; l->name; l++) size++; return size; } LUALIB_API void luaI_openlib (lua_State *L, const char *libname, const luaL_Reg *l, int nup) { if (libname) { int size = libsize(l); /* check whether lib already exists */ luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1); lua_getfield(L, -1, libname); /* get _LOADED[libname] */ if (!lua_istable(L, -1)) { /* not found? */ lua_pop(L, 1); /* remove previous result */ /* try global variable (and create one if it does not exist) */ if (luaL_findtable(L, LUA_GLOBALSINDEX, libname, size) != NULL) luaL_error(L, "name conflict for module " LUA_QS, libname); lua_pushvalue(L, -1); lua_setfield(L, -3, libname); /* _LOADED[libname] = new table */ } lua_remove(L, -2); /* remove _LOADED table */ lua_insert(L, -(nup+1)); /* move library table to below upvalues */ } for (; l->name; l++) { int i; for (i=0; ifunc, nup); lua_setfield(L, -(nup+2), l->name); } lua_pop(L, nup); /* remove upvalues */ } /* ** {====================================================== ** getn-setn: size for arrays ** ======================================================= */ #if defined(LUA_COMPAT_GETN) static int checkint (lua_State *L, int topop) { int n = (lua_type(L, -1) == LUA_TNUMBER) ? lua_tointeger(L, -1) : -1; lua_pop(L, topop); return n; } static void getsizes (lua_State *L) { lua_getfield(L, LUA_REGISTRYINDEX, "LUA_SIZES"); if (lua_isnil(L, -1)) { /* no `size' table? */ lua_pop(L, 1); /* remove nil */ lua_newtable(L); /* create it */ lua_pushvalue(L, -1); /* `size' will be its own metatable */ lua_setmetatable(L, -2); lua_pushliteral(L, "kv"); lua_setfield(L, -2, "__mode"); /* metatable(N).__mode = "kv" */ lua_pushvalue(L, -1); lua_setfield(L, LUA_REGISTRYINDEX, "LUA_SIZES"); /* store in register */ } } LUALIB_API void luaL_setn (lua_State *L, int t, int n) { t = abs_index(L, t); lua_pushliteral(L, "n"); lua_rawget(L, t); if (checkint(L, 1) >= 0) { /* is there a numeric field `n'? */ lua_pushliteral(L, "n"); /* use it */ lua_pushinteger(L, n); lua_rawset(L, t); } else { /* use `sizes' */ getsizes(L); lua_pushvalue(L, t); lua_pushinteger(L, n); lua_rawset(L, -3); /* sizes[t] = n */ lua_pop(L, 1); /* remove `sizes' */ } } LUALIB_API int luaL_getn (lua_State *L, int t) { int n; t = abs_index(L, t); lua_pushliteral(L, "n"); /* try t.n */ lua_rawget(L, t); if ((n = checkint(L, 1)) >= 0) return n; getsizes(L); /* else try sizes[t] */ lua_pushvalue(L, t); lua_rawget(L, -2); if ((n = checkint(L, 2)) >= 0) return n; return (int)lua_objlen(L, t); } #endif /* }====================================================== */ LUALIB_API const char *luaL_gsub (lua_State *L, const char *s, const char *p, const char *r) { const char *wild; size_t l = strlen(p); luaL_Buffer b; luaL_buffinit(L, &b); while ((wild = strstr(s, p)) != NULL) { luaL_addlstring(&b, s, wild - s); /* push prefix */ luaL_addstring(&b, r); /* push replacement in place of pattern */ s = wild + l; /* continue after `p' */ } luaL_addstring(&b, s); /* push last suffix */ luaL_pushresult(&b); return lua_tostring(L, -1); } LUALIB_API const char *luaL_findtable (lua_State *L, int idx, const char *fname, int szhint) { const char *e; lua_pushvalue(L, idx); do { e = strchr(fname, '.'); if (e == NULL) e = fname + strlen(fname); lua_pushlstring(L, fname, e - fname); lua_rawget(L, -2); if (lua_isnil(L, -1)) { /* no such field? */ lua_pop(L, 1); /* remove this nil */ lua_createtable(L, 0, (*e == '.' ? 1 : szhint)); /* new table for field */ lua_pushlstring(L, fname, e - fname); lua_pushvalue(L, -2); lua_settable(L, -4); /* set new table into field */ } else if (!lua_istable(L, -1)) { /* field has a non-table value? */ lua_pop(L, 2); /* remove table and value */ return fname; /* return problematic part of the name */ } lua_remove(L, -2); /* remove previous table */ fname = e + 1; } while (*e == '.'); return NULL; } /* ** {====================================================== ** Generic Buffer manipulation ** ======================================================= */ #define bufflen(B) ((B)->p - (B)->buffer) #define bufffree(B) ((size_t)(LUAL_BUFFERSIZE - bufflen(B))) #define LIMIT (LUA_MINSTACK/2) static int emptybuffer (luaL_Buffer *B) { size_t l = bufflen(B); if (l == 0) return 0; /* put nothing on stack */ else { lua_pushlstring(B->L, B->buffer, l); B->p = B->buffer; B->lvl++; return 1; } } static void adjuststack (luaL_Buffer *B) { if (B->lvl > 1) { lua_State *L = B->L; int toget = 1; /* number of levels to concat */ size_t toplen = lua_strlen(L, -1); do { size_t l = lua_strlen(L, -(toget+1)); if (B->lvl - toget + 1 >= LIMIT || toplen > l) { toplen += l; toget++; } else break; } while (toget < B->lvl); lua_concat(L, toget); B->lvl = B->lvl - toget + 1; } } LUALIB_API char *luaL_prepbuffer (luaL_Buffer *B) { if (emptybuffer(B)) adjuststack(B); return B->buffer; } LUALIB_API void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l) { while (l--) luaL_addchar(B, *s++); } LUALIB_API void luaL_addstring (luaL_Buffer *B, const char *s) { luaL_addlstring(B, s, strlen(s)); } LUALIB_API void luaL_pushresult (luaL_Buffer *B) { emptybuffer(B); lua_concat(B->L, B->lvl); B->lvl = 1; } LUALIB_API void luaL_addvalue (luaL_Buffer *B) { lua_State *L = B->L; size_t vl; const char *s = lua_tolstring(L, -1, &vl); if (vl <= bufffree(B)) { /* fit into buffer? */ memcpy(B->p, s, vl); /* put it there */ B->p += vl; lua_pop(L, 1); /* remove from stack */ } else { if (emptybuffer(B)) lua_insert(L, -2); /* put buffer before new value */ B->lvl++; /* add new value into B stack */ adjuststack(B); } } LUALIB_API void luaL_buffinit (lua_State *L, luaL_Buffer *B) { B->L = L; B->p = B->buffer; B->lvl = 0; } /* }====================================================== */ LUALIB_API int luaL_ref (lua_State *L, int t) { int ref; t = abs_index(L, t); if (lua_isnil(L, -1)) { lua_pop(L, 1); /* remove from stack */ return LUA_REFNIL; /* `nil' has a unique fixed reference */ } lua_rawgeti(L, t, FREELIST_REF); /* get first free element */ ref = (int)lua_tointeger(L, -1); /* ref = t[FREELIST_REF] */ lua_pop(L, 1); /* remove it from stack */ if (ref != 0) { /* any free element? */ lua_rawgeti(L, t, ref); /* remove it from list */ lua_rawseti(L, t, FREELIST_REF); /* (t[FREELIST_REF] = t[ref]) */ } else { /* no free elements */ ref = (int)lua_objlen(L, t); ref++; /* create new reference */ } lua_rawseti(L, t, ref); return ref; } LUALIB_API void luaL_unref (lua_State *L, int t, int ref) { if (ref >= 0) { t = abs_index(L, t); lua_rawgeti(L, t, FREELIST_REF); lua_rawseti(L, t, ref); /* t[ref] = t[FREELIST_REF] */ lua_pushinteger(L, ref); lua_rawseti(L, t, FREELIST_REF); /* t[FREELIST_REF] = ref */ } } /* ** {====================================================== ** Load functions ** ======================================================= */ typedef struct LoadF { int extraline; FILE *f; char buff[LUAL_BUFFERSIZE]; } LoadF; static const char *getF (lua_State *L, void *ud, size_t *size) { LoadF *lf = (LoadF *)ud; (void)L; if (lf->extraline) { lf->extraline = 0; *size = 1; return "\n"; } if (feof(lf->f)) return NULL; *size = fread(lf->buff, 1, sizeof(lf->buff), lf->f); return (*size > 0) ? lf->buff : NULL; } static int errfile (lua_State *L, const char *what, int fnameindex) { const char *serr = strerror(errno); const char *filename = lua_tostring(L, fnameindex) + 1; lua_pushfstring(L, "cannot %s %s: %s", what, filename, serr); lua_remove(L, fnameindex); return LUA_ERRFILE; } LUALIB_API int luaL_loadfile (lua_State *L, const char *filename) { LoadF lf; int status, readstatus; int c; int fnameindex = lua_gettop(L) + 1; /* index of filename on the stack */ lf.extraline = 0; if (filename == NULL) { lua_pushliteral(L, "=stdin"); lf.f = stdin; } else { lua_pushfstring(L, "@%s", filename); lf.f = fopen(filename, "r"); if (lf.f == NULL) return errfile(L, "open", fnameindex); } c = getc(lf.f); if (c == '#') { /* Unix exec. file? */ lf.extraline = 1; while ((c = getc(lf.f)) != EOF && c != '\n') ; /* skip first line */ if (c == '\n') c = getc(lf.f); } if (c == LUA_SIGNATURE[0] && filename) { /* binary file? */ lf.f = freopen(filename, "rb", lf.f); /* reopen in binary mode */ if (lf.f == NULL) return errfile(L, "reopen", fnameindex); /* skip eventual `#!...' */ while ((c = getc(lf.f)) != EOF && c != LUA_SIGNATURE[0]) ; lf.extraline = 0; } ungetc(c, lf.f); status = lua_load(L, getF, &lf, lua_tostring(L, -1)); readstatus = ferror(lf.f); if (filename) fclose(lf.f); /* close file (even in case of errors) */ if (readstatus) { lua_settop(L, fnameindex); /* ignore results from `lua_load' */ return errfile(L, "read", fnameindex); } lua_remove(L, fnameindex); return status; } typedef struct LoadS { const char *s; size_t size; } LoadS; static const char *getS (lua_State *L, void *ud, size_t *size) { LoadS *ls = (LoadS *)ud; (void)L; if (ls->size == 0) return NULL; *size = ls->size; ls->size = 0; return ls->s; } LUALIB_API int luaL_loadbuffer (lua_State *L, const char *buff, size_t size, const char *name) { LoadS ls; ls.s = buff; ls.size = size; return lua_load(L, getS, &ls, name); } LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s) { return luaL_loadbuffer(L, s, strlen(s), s); } /* }====================================================== */ static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { (void)ud; (void)osize; if (nsize == 0) { free(ptr); return NULL; } else return realloc(ptr, nsize); } static int panic (lua_State *L) { (void)L; /* to avoid warnings */ fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n", lua_tostring(L, -1)); return 0; } LUALIB_API lua_State *luaL_newstate (void) { lua_State *L = lua_newstate(l_alloc, NULL); if (L) lua_atpanic(L, &panic); return L; } ================================================ FILE: src/lauxlib.h ================================================ /* ** $Id: lauxlib.h,v 1.88.1.1 2007/12/27 13:02:25 roberto Exp $ ** Auxiliary functions for building Lua libraries ** See Copyright Notice in lua.h */ #ifndef lauxlib_h #define lauxlib_h #include #include #include "lua.h" #if defined(LUA_COMPAT_GETN) LUALIB_API int (luaL_getn) (lua_State *L, int t); LUALIB_API void (luaL_setn) (lua_State *L, int t, int n); #else #define luaL_getn(L,i) ((int)lua_objlen(L, i)) #define luaL_setn(L,i,j) ((void)0) /* no op! */ #endif #if defined(LUA_COMPAT_OPENLIB) #define luaI_openlib luaL_openlib #endif /* extra error code for `luaL_load' */ #define LUA_ERRFILE (LUA_ERRERR+1) typedef struct luaL_Reg { const char *name; lua_CFunction func; } luaL_Reg; LUALIB_API void (luaI_openlib) (lua_State *L, const char *libname, const luaL_Reg *l, int nup); LUALIB_API void (luaL_register) (lua_State *L, const char *libname, const luaL_Reg *l); LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e); LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e); LUALIB_API int (luaL_typerror) (lua_State *L, int narg, const char *tname); LUALIB_API int (luaL_argerror) (lua_State *L, int numarg, const char *extramsg); LUALIB_API const char *(luaL_checklstring) (lua_State *L, int numArg, size_t *l); LUALIB_API const char *(luaL_optlstring) (lua_State *L, int numArg, const char *def, size_t *l); LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int numArg); LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int nArg, lua_Number def); LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int numArg); LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int nArg, lua_Integer def); LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg); LUALIB_API void (luaL_checktype) (lua_State *L, int narg, int t); LUALIB_API void (luaL_checkany) (lua_State *L, int narg); LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname); LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname); LUALIB_API void (luaL_where) (lua_State *L, int lvl); LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...); LUALIB_API int (luaL_checkoption) (lua_State *L, int narg, const char *def, const char *const lst[]); LUALIB_API int (luaL_ref) (lua_State *L, int t); LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref); LUALIB_API int (luaL_loadfile) (lua_State *L, const char *filename); LUALIB_API int (luaL_loadbuffer) (lua_State *L, const char *buff, size_t sz, const char *name); LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s); LUALIB_API lua_State *(luaL_newstate) (void); LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p, const char *r); LUALIB_API const char *(luaL_findtable) (lua_State *L, int idx, const char *fname, int szhint); /* ** =============================================================== ** some useful macros ** =============================================================== */ #define luaL_argcheck(L, cond,numarg,extramsg) \ ((void)((cond) || luaL_argerror(L, (numarg), (extramsg)))) #define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL)) #define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL)) #define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) #define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) #define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) #define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) #define luaL_typename(L,i) lua_typename(L, lua_type(L,(i))) #define luaL_dofile(L, fn) \ (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) #define luaL_dostring(L, s) \ (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) #define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) #define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n))) /* ** {====================================================== ** Generic Buffer manipulation ** ======================================================= */ typedef struct luaL_Buffer { char *p; /* current position in buffer */ int lvl; /* number of strings in the stack (level) */ lua_State *L; char buffer[LUAL_BUFFERSIZE]; } luaL_Buffer; #define luaL_addchar(B,c) \ ((void)((B)->p < ((B)->buffer+LUAL_BUFFERSIZE) || luaL_prepbuffer(B)), \ (*(B)->p++ = (char)(c))) /* compatibility only */ #define luaL_putchar(B,c) luaL_addchar(B,c) #define luaL_addsize(B,n) ((B)->p += (n)) LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B); LUALIB_API char *(luaL_prepbuffer) (luaL_Buffer *B); LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l); LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s); LUALIB_API void (luaL_addvalue) (luaL_Buffer *B); LUALIB_API void (luaL_pushresult) (luaL_Buffer *B); /* }====================================================== */ /* compatibility with ref system */ /* pre-defined references */ #define LUA_NOREF (-2) #define LUA_REFNIL (-1) #define lua_ref(L,lock) ((lock) ? luaL_ref(L, LUA_REGISTRYINDEX) : \ (lua_pushstring(L, "unlocked references are obsolete"), lua_error(L), 0)) #define lua_unref(L,ref) luaL_unref(L, LUA_REGISTRYINDEX, (ref)) #define lua_getref(L,ref) lua_rawgeti(L, LUA_REGISTRYINDEX, (ref)) #define luaL_reg luaL_Reg #endif ================================================ FILE: src/lbaselib.c ================================================ /* ** $Id: lbaselib.c,v 1.191.1.6 2008/02/14 16:46:22 roberto Exp $ ** Basic library ** See Copyright Notice in lua.h */ #include #ifdef __NetBSD__ #include #else #include #endif #include #include #include #define lbaselib_c #define LUA_LIB #include "lua.h" #include "lauxlib.h" #include "lualib.h" static int luaB_print (lua_State *L) { int n = lua_gettop(L); /* number of arguments */ int i, y, x; lua_getglobal(L, "tostring"); for (i=1; i<=n; i++) { const char *s; lua_pushvalue(L, -1); /* function to be called */ lua_pushvalue(L, i); /* value to print */ lua_call(L, 1, 1); s = lua_tostring(L, -1); /* get result */ if (s == NULL) return luaL_error(L, LUA_QL("tostring") " must return a string to " LUA_QL("print")); if (i>1) addstr("\t"); addstr(s); lua_pop(L, 1); /* pop result */ } getyx(stdscr, y, x); mvprintw(y+1, x=0, ""); refresh(); return 0; } static int luaB_tonumber (lua_State *L) { int base = luaL_optint(L, 2, 10); if (base == 10) { /* standard conversion */ luaL_checkany(L, 1); if (lua_isnumber(L, 1)) { lua_pushnumber(L, lua_tonumber(L, 1)); return 1; } } else { const char *s1 = luaL_checkstring(L, 1); char *s2; unsigned long n; luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range"); n = strtoul(s1, &s2, base); if (s1 != s2) { /* at least one valid digit? */ while (isspace((unsigned char)(*s2))) s2++; /* skip trailing spaces */ if (*s2 == '\0') { /* no invalid trailing characters? */ lua_pushnumber(L, (lua_Number)n); return 1; } } } lua_pushnil(L); /* else not a number */ return 1; } static int luaB_error (lua_State *L) { int level = luaL_optint(L, 2, 1); lua_settop(L, 1); if (lua_isstring(L, 1) && level > 0) { /* add extra information? */ luaL_where(L, level); lua_pushvalue(L, 1); lua_concat(L, 2); } return lua_error(L); } static int luaB_getmetatable (lua_State *L) { luaL_checkany(L, 1); if (!lua_getmetatable(L, 1)) { lua_pushnil(L); return 1; /* no metatable */ } luaL_getmetafield(L, 1, "__metatable"); return 1; /* returns either __metatable field (if present) or metatable */ } static int luaB_setmetatable (lua_State *L) { int t = lua_type(L, 2); luaL_checktype(L, 1, LUA_TTABLE); luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2, "nil or table expected"); if (luaL_getmetafield(L, 1, "__metatable")) luaL_error(L, "cannot change a protected metatable"); lua_settop(L, 2); lua_setmetatable(L, 1); return 1; } static void getfunc (lua_State *L, int opt) { if (lua_isfunction(L, 1)) lua_pushvalue(L, 1); else { lua_Debug ar; int level = opt ? luaL_optint(L, 1, 1) : luaL_checkint(L, 1); luaL_argcheck(L, level >= 0, 1, "level must be non-negative"); if (lua_getstack(L, level, &ar) == 0) luaL_argerror(L, 1, "invalid level"); lua_getinfo(L, "f", &ar); if (lua_isnil(L, -1)) luaL_error(L, "no function environment for tail call at level %d", level); } } static int luaB_getfenv (lua_State *L) { getfunc(L, 1); if (lua_iscfunction(L, -1)) /* is a C function? */ lua_pushvalue(L, LUA_GLOBALSINDEX); /* return the thread's global env. */ else lua_getfenv(L, -1); return 1; } static int luaB_setfenv (lua_State *L) { luaL_checktype(L, 2, LUA_TTABLE); getfunc(L, 0); lua_pushvalue(L, 2); if (lua_isnumber(L, 1) && lua_tonumber(L, 1) == 0) { /* change environment of current thread */ lua_pushthread(L); lua_insert(L, -2); lua_setfenv(L, -2); return 0; } else if (lua_iscfunction(L, -2) || lua_setfenv(L, -2) == 0) luaL_error(L, LUA_QL("setfenv") " cannot change environment of given object"); return 1; } static int luaB_rawequal (lua_State *L) { luaL_checkany(L, 1); luaL_checkany(L, 2); lua_pushboolean(L, lua_rawequal(L, 1, 2)); return 1; } static int luaB_rawget (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); luaL_checkany(L, 2); lua_settop(L, 2); lua_rawget(L, 1); return 1; } static int luaB_rawset (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); luaL_checkany(L, 2); luaL_checkany(L, 3); lua_settop(L, 3); lua_rawset(L, 1); return 1; } static int luaB_gcinfo (lua_State *L) { lua_pushinteger(L, lua_getgccount(L)); return 1; } static int luaB_collectgarbage (lua_State *L) { static const char *const opts[] = {"stop", "restart", "collect", "count", "step", "setpause", "setstepmul", NULL}; static const int optsnum[] = {LUA_GCSTOP, LUA_GCRESTART, LUA_GCCOLLECT, LUA_GCCOUNT, LUA_GCSTEP, LUA_GCSETPAUSE, LUA_GCSETSTEPMUL}; int o = luaL_checkoption(L, 1, "collect", opts); int ex = luaL_optint(L, 2, 0); int res = lua_gc(L, optsnum[o], ex); switch (optsnum[o]) { case LUA_GCCOUNT: { int b = lua_gc(L, LUA_GCCOUNTB, 0); lua_pushnumber(L, res + ((lua_Number)b/1024)); return 1; } case LUA_GCSTEP: { lua_pushboolean(L, res); return 1; } default: { lua_pushnumber(L, res); return 1; } } } static int luaB_type (lua_State *L) { luaL_checkany(L, 1); lua_pushstring(L, luaL_typename(L, 1)); return 1; } static int luaB_next (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 2); /* create a 2nd argument if there isn't one */ if (lua_next(L, 1)) return 2; else { lua_pushnil(L); return 1; } } static int luaB_pairs (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */ lua_pushvalue(L, 1); /* state, */ lua_pushnil(L); /* and initial value */ return 3; } static int ipairsaux (lua_State *L) { int i = luaL_checkint(L, 2); luaL_checktype(L, 1, LUA_TTABLE); i++; /* next value */ lua_pushinteger(L, i); lua_rawgeti(L, 1, i); return (lua_isnil(L, -1)) ? 0 : 2; } static int luaB_ipairs (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */ lua_pushvalue(L, 1); /* state, */ lua_pushinteger(L, 0); /* and initial value */ return 3; } static int luaB_assert (lua_State *L) { luaL_checkany(L, 1); if (!lua_toboolean(L, 1)) return luaL_error(L, "%s", luaL_optstring(L, 2, "assertion failed!")); return lua_gettop(L); } static int luaB_unpack (lua_State *L) { int i, e, n; luaL_checktype(L, 1, LUA_TTABLE); i = luaL_optint(L, 2, 1); e = luaL_opt(L, luaL_checkint, 3, luaL_getn(L, 1)); if (i > e) return 0; /* empty range */ n = e - i + 1; /* number of elements */ if (n <= 0 || !lua_checkstack(L, n)) /* n <= 0 means arith. overflow */ return luaL_error(L, "too many results to unpack"); lua_rawgeti(L, 1, i); /* push arg[i] (avoiding overflow problems) */ while (i++ < e) /* push arg[i + 1...e] */ lua_rawgeti(L, 1, i); return n; } static int luaB_select (lua_State *L) { int n = lua_gettop(L); if (lua_type(L, 1) == LUA_TSTRING && *lua_tostring(L, 1) == '#') { lua_pushinteger(L, n-1); return 1; } else { int i = luaL_checkint(L, 1); if (i < 0) i = n + i; else if (i > n) i = n; luaL_argcheck(L, 1 <= i, 1, "index out of range"); return n - i; } } static int luaB_pcall (lua_State *L) { int status; luaL_checkany(L, 1); status = lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0); lua_pushboolean(L, (status == 0)); lua_insert(L, 1); return lua_gettop(L); /* return status + all results */ } static int luaB_xpcall (lua_State *L) { int status; luaL_checkany(L, 2); lua_settop(L, 2); lua_insert(L, 1); /* put error function under function to be called */ status = lua_pcall(L, 0, LUA_MULTRET, 1); lua_pushboolean(L, (status == 0)); lua_replace(L, 1); return lua_gettop(L); /* return status + all results */ } static int luaB_tostring (lua_State *L) { luaL_checkany(L, 1); if (luaL_callmeta(L, 1, "__tostring")) /* is there a metafield? */ return 1; /* use its value */ switch (lua_type(L, 1)) { case LUA_TNUMBER: lua_pushstring(L, lua_tostring(L, 1)); break; case LUA_TSTRING: lua_pushvalue(L, 1); break; case LUA_TBOOLEAN: lua_pushstring(L, (lua_toboolean(L, 1) ? "true" : "false")); break; case LUA_TNIL: lua_pushliteral(L, "nil"); break; default: lua_pushfstring(L, "%s: %p", luaL_typename(L, 1), lua_topointer(L, 1)); break; } return 1; } static int luaB_newproxy (lua_State *L) { lua_settop(L, 1); lua_newuserdata(L, 0); /* create proxy */ if (lua_toboolean(L, 1) == 0) return 1; /* no metatable */ else if (lua_isboolean(L, 1)) { lua_newtable(L); /* create a new metatable `m' ... */ lua_pushvalue(L, -1); /* ... and mark `m' as a valid metatable */ lua_pushboolean(L, 1); lua_rawset(L, lua_upvalueindex(1)); /* weaktable[m] = true */ } else { int validproxy = 0; /* to check if weaktable[metatable(u)] == true */ if (lua_getmetatable(L, 1)) { lua_rawget(L, lua_upvalueindex(1)); validproxy = lua_toboolean(L, -1); lua_pop(L, 1); /* remove value */ } luaL_argcheck(L, validproxy, 1, "boolean or proxy expected"); lua_getmetatable(L, 1); /* metatable is valid; get it */ } lua_setmetatable(L, 2); return 1; } static const luaL_Reg base_funcs[] = { {"assert", luaB_assert}, {"collectgarbage", luaB_collectgarbage}, /* no 'dofile' without sandboxing it */ {"error", luaB_error}, {"gcinfo", luaB_gcinfo}, {"getfenv", luaB_getfenv}, {"getmetatable", luaB_getmetatable}, /* no 'loadfile' without sandboxing it */ /* no 'load' without sandboxing it */ /* no 'loadstring' without sandboxing it */ {"next", luaB_next}, {"pcall", luaB_pcall}, {"print", luaB_print}, {"rawequal", luaB_rawequal}, {"rawget", luaB_rawget}, {"rawset", luaB_rawset}, {"select", luaB_select}, {"setfenv", luaB_setfenv}, {"setmetatable", luaB_setmetatable}, {"tonumber", luaB_tonumber}, {"tostring", luaB_tostring}, {"type", luaB_type}, {"unpack", luaB_unpack}, {"xpcall", luaB_xpcall}, {NULL, NULL} }; /* ** {====================================================== ** Coroutine library ** ======================================================= */ #define CO_RUN 0 /* running */ #define CO_SUS 1 /* suspended */ #define CO_NOR 2 /* 'normal' (it resumed another coroutine) */ #define CO_DEAD 3 static const char *const statnames[] = {"running", "suspended", "normal", "dead"}; static int costatus (lua_State *L, lua_State *co) { if (L == co) return CO_RUN; switch (lua_status(co)) { case LUA_YIELD: return CO_SUS; case 0: { lua_Debug ar; if (lua_getstack(co, 0, &ar) > 0) /* does it have frames? */ return CO_NOR; /* it is running */ else if (lua_gettop(co) == 0) return CO_DEAD; else return CO_SUS; /* initial state */ } default: /* some error occured */ return CO_DEAD; } } static int luaB_costatus (lua_State *L) { lua_State *co = lua_tothread(L, 1); luaL_argcheck(L, co, 1, "coroutine expected"); lua_pushstring(L, statnames[costatus(L, co)]); return 1; } static int auxresume (lua_State *L, lua_State *co, int narg) { int status = costatus(L, co); if (!lua_checkstack(co, narg)) luaL_error(L, "too many arguments to resume"); if (status != CO_SUS) { lua_pushfstring(L, "cannot resume %s coroutine", statnames[status]); return -1; /* error flag */ } lua_xmove(L, co, narg); lua_setlevel(L, co); status = lua_resume(co, narg); if (status == 0 || status == LUA_YIELD) { int nres = lua_gettop(co); if (!lua_checkstack(L, nres + 1)) luaL_error(L, "too many results to resume"); lua_xmove(co, L, nres); /* move yielded values */ return nres; } else { lua_xmove(co, L, 1); /* move error message */ return -1; /* error flag */ } } static int luaB_coresume (lua_State *L) { lua_State *co = lua_tothread(L, 1); int r; luaL_argcheck(L, co, 1, "coroutine expected"); r = auxresume(L, co, lua_gettop(L) - 1); if (r < 0) { lua_pushboolean(L, 0); lua_insert(L, -2); return 2; /* return false + error message */ } else { lua_pushboolean(L, 1); lua_insert(L, -(r + 1)); return r + 1; /* return true + `resume' returns */ } } static int luaB_auxwrap (lua_State *L) { lua_State *co = lua_tothread(L, lua_upvalueindex(1)); int r = auxresume(L, co, lua_gettop(L)); if (r < 0) { if (lua_isstring(L, -1)) { /* error object is a string? */ luaL_where(L, 1); /* add extra info */ lua_insert(L, -2); lua_concat(L, 2); } lua_error(L); /* propagate error */ } return r; } static int luaB_cocreate (lua_State *L) { lua_State *NL = lua_newthread(L); luaL_argcheck(L, lua_isfunction(L, 1) && !lua_iscfunction(L, 1), 1, "Lua function expected"); lua_pushvalue(L, 1); /* move function to top */ lua_xmove(L, NL, 1); /* move function from L to NL */ return 1; } static int luaB_cowrap (lua_State *L) { luaB_cocreate(L); lua_pushcclosure(L, luaB_auxwrap, 1); return 1; } static int luaB_yield (lua_State *L) { return lua_yield(L, lua_gettop(L)); } static int luaB_corunning (lua_State *L) { if (lua_pushthread(L)) lua_pushnil(L); /* main thread is not a coroutine */ return 1; } static const luaL_Reg co_funcs[] = { {"create", luaB_cocreate}, {"resume", luaB_coresume}, {"running", luaB_corunning}, {"status", luaB_costatus}, {"wrap", luaB_cowrap}, {"yield", luaB_yield}, {NULL, NULL} }; /* }====================================================== */ static void auxopen (lua_State *L, const char *name, lua_CFunction f, lua_CFunction u) { lua_pushcfunction(L, u); lua_pushcclosure(L, f, 1); lua_setfield(L, -2, name); } static void base_open (lua_State *L) { /* set global _G */ lua_pushvalue(L, LUA_GLOBALSINDEX); lua_setglobal(L, "_G"); /* open lib into global table */ luaL_register(L, "_G", base_funcs); lua_pushliteral(L, LUA_VERSION); lua_setglobal(L, "_VERSION"); /* set global _VERSION */ /* `ipairs' and `pairs' need auxiliary functions as upvalues */ auxopen(L, "ipairs", luaB_ipairs, ipairsaux); auxopen(L, "pairs", luaB_pairs, luaB_next); /* `newproxy' needs a weaktable as upvalue */ lua_createtable(L, 0, 1); /* new table `w' */ lua_pushvalue(L, -1); /* `w' will be its own metatable */ lua_setmetatable(L, -2); lua_pushliteral(L, "kv"); lua_setfield(L, -2, "__mode"); /* metatable(w).__mode = "kv" */ lua_pushcclosure(L, luaB_newproxy, 1); lua_setglobal(L, "newproxy"); /* set global `newproxy' */ } LUALIB_API int luaopen_base (lua_State *L) { base_open(L); luaL_register(L, LUA_COLIBNAME, co_funcs); return 2; } ================================================ FILE: src/lcode.c ================================================ /* ** $Id: lcode.c,v 2.25.1.5 2011/01/31 14:53:16 roberto Exp $ ** Code generator for Lua ** See Copyright Notice in lua.h */ #include #define lcode_c #define LUA_CORE #include "lua.h" #include "lcode.h" #include "ldebug.h" #include "ldo.h" #include "lgc.h" #include "llex.h" #include "lmem.h" #include "lobject.h" #include "lopcodes.h" #include "lparser.h" #include "ltable.h" #define hasjumps(e) ((e)->t != (e)->f) static int isnumeral(expdesc *e) { return (e->k == VKNUM && e->t == NO_JUMP && e->f == NO_JUMP); } void luaK_nil (FuncState *fs, int from, int n) { Instruction *previous; if (fs->pc > fs->lasttarget) { /* no jumps to current position? */ if (fs->pc == 0) { /* function start? */ if (from >= fs->nactvar) return; /* positions are already clean */ } else { previous = &fs->f->code[fs->pc-1]; if (GET_OPCODE(*previous) == OP_LOADNIL) { int pfrom = GETARG_A(*previous); int pto = GETARG_B(*previous); if (pfrom <= from && from <= pto+1) { /* can connect both? */ if (from+n-1 > pto) SETARG_B(*previous, from+n-1); return; } } } } luaK_codeABC(fs, OP_LOADNIL, from, from+n-1, 0); /* else no optimization */ } int luaK_jump (FuncState *fs) { int jpc = fs->jpc; /* save list of jumps to here */ int j; fs->jpc = NO_JUMP; j = luaK_codeAsBx(fs, OP_JMP, 0, NO_JUMP); luaK_concat(fs, &j, jpc); /* keep them on hold */ return j; } void luaK_ret (FuncState *fs, int first, int nret) { luaK_codeABC(fs, OP_RETURN, first, nret+1, 0); } static int condjump (FuncState *fs, OpCode op, int A, int B, int C) { luaK_codeABC(fs, op, A, B, C); return luaK_jump(fs); } static void fixjump (FuncState *fs, int pc, int dest) { Instruction *jmp = &fs->f->code[pc]; int offset = dest-(pc+1); lua_assert(dest != NO_JUMP); if (abs(offset) > MAXARG_sBx) luaX_syntaxerror(fs->ls, "control structure too long"); SETARG_sBx(*jmp, offset); } /* ** returns current `pc' and marks it as a jump target (to avoid wrong ** optimizations with consecutive instructions not in the same basic block). */ int luaK_getlabel (FuncState *fs) { fs->lasttarget = fs->pc; return fs->pc; } static int getjump (FuncState *fs, int pc) { int offset = GETARG_sBx(fs->f->code[pc]); if (offset == NO_JUMP) /* point to itself represents end of list */ return NO_JUMP; /* end of list */ else return (pc+1)+offset; /* turn offset into absolute position */ } static Instruction *getjumpcontrol (FuncState *fs, int pc) { Instruction *pi = &fs->f->code[pc]; if (pc >= 1 && testTMode(GET_OPCODE(*(pi-1)))) return pi-1; else return pi; } /* ** check whether list has any jump that do not produce a value ** (or produce an inverted value) */ static int need_value (FuncState *fs, int list) { for (; list != NO_JUMP; list = getjump(fs, list)) { Instruction i = *getjumpcontrol(fs, list); if (GET_OPCODE(i) != OP_TESTSET) return 1; } return 0; /* not found */ } static int patchtestreg (FuncState *fs, int node, int reg) { Instruction *i = getjumpcontrol(fs, node); if (GET_OPCODE(*i) != OP_TESTSET) return 0; /* cannot patch other instructions */ if (reg != NO_REG && reg != GETARG_B(*i)) SETARG_A(*i, reg); else /* no register to put value or register already has the value */ *i = CREATE_ABC(OP_TEST, GETARG_B(*i), 0, GETARG_C(*i)); return 1; } static void removevalues (FuncState *fs, int list) { for (; list != NO_JUMP; list = getjump(fs, list)) patchtestreg(fs, list, NO_REG); } static void patchlistaux (FuncState *fs, int list, int vtarget, int reg, int dtarget) { while (list != NO_JUMP) { int next = getjump(fs, list); if (patchtestreg(fs, list, reg)) fixjump(fs, list, vtarget); else fixjump(fs, list, dtarget); /* jump to default target */ list = next; } } static void dischargejpc (FuncState *fs) { patchlistaux(fs, fs->jpc, fs->pc, NO_REG, fs->pc); fs->jpc = NO_JUMP; } void luaK_patchlist (FuncState *fs, int list, int target) { if (target == fs->pc) luaK_patchtohere(fs, list); else { lua_assert(target < fs->pc); patchlistaux(fs, list, target, NO_REG, target); } } void luaK_patchtohere (FuncState *fs, int list) { luaK_getlabel(fs); luaK_concat(fs, &fs->jpc, list); } void luaK_concat (FuncState *fs, int *l1, int l2) { if (l2 == NO_JUMP) return; else if (*l1 == NO_JUMP) *l1 = l2; else { int list = *l1; int next; while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */ list = next; fixjump(fs, list, l2); } } void luaK_checkstack (FuncState *fs, int n) { int newstack = fs->freereg + n; if (newstack > fs->f->maxstacksize) { if (newstack >= MAXSTACK) luaX_syntaxerror(fs->ls, "function or expression too complex"); fs->f->maxstacksize = cast_byte(newstack); } } void luaK_reserveregs (FuncState *fs, int n) { luaK_checkstack(fs, n); fs->freereg += n; } static void freereg (FuncState *fs, int reg) { if (!ISK(reg) && reg >= fs->nactvar) { fs->freereg--; lua_assert(reg == fs->freereg); } } static void freeexp (FuncState *fs, expdesc *e) { if (e->k == VNONRELOC) freereg(fs, e->u.s.info); } static int addk (FuncState *fs, TValue *k, TValue *v) { lua_State *L = fs->L; TValue *idx = luaH_set(L, fs->h, k); Proto *f = fs->f; int oldsize = f->sizek; if (ttisnumber(idx)) { lua_assert(luaO_rawequalObj(&fs->f->k[cast_int(nvalue(idx))], v)); return cast_int(nvalue(idx)); } else { /* constant not found; create a new entry */ setnvalue(idx, cast_num(fs->nk)); luaM_growvector(L, f->k, fs->nk, f->sizek, TValue, MAXARG_Bx, "constant table overflow"); while (oldsize < f->sizek) setnilvalue(&f->k[oldsize++]); setobj(L, &f->k[fs->nk], v); luaC_barrier(L, f, v); return fs->nk++; } } int luaK_stringK (FuncState *fs, TString *s) { TValue o; setsvalue(fs->L, &o, s); return addk(fs, &o, &o); } int luaK_numberK (FuncState *fs, lua_Number r) { TValue o; setnvalue(&o, r); return addk(fs, &o, &o); } static int boolK (FuncState *fs, int b) { TValue o; setbvalue(&o, b); return addk(fs, &o, &o); } static int nilK (FuncState *fs) { TValue k, v; setnilvalue(&v); /* cannot use nil as key; instead use table itself to represent nil */ sethvalue(fs->L, &k, fs->h); return addk(fs, &k, &v); } void luaK_setreturns (FuncState *fs, expdesc *e, int nresults) { if (e->k == VCALL) { /* expression is an open function call? */ SETARG_C(getcode(fs, e), nresults+1); } else if (e->k == VVARARG) { SETARG_B(getcode(fs, e), nresults+1); SETARG_A(getcode(fs, e), fs->freereg); luaK_reserveregs(fs, 1); } } void luaK_setoneret (FuncState *fs, expdesc *e) { if (e->k == VCALL) { /* expression is an open function call? */ e->k = VNONRELOC; e->u.s.info = GETARG_A(getcode(fs, e)); } else if (e->k == VVARARG) { SETARG_B(getcode(fs, e), 2); e->k = VRELOCABLE; /* can relocate its simple result */ } } void luaK_dischargevars (FuncState *fs, expdesc *e) { switch (e->k) { case VLOCAL: { e->k = VNONRELOC; break; } case VUPVAL: { e->u.s.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.s.info, 0); e->k = VRELOCABLE; break; } case VGLOBAL: { e->u.s.info = luaK_codeABx(fs, OP_GETGLOBAL, 0, e->u.s.info); e->k = VRELOCABLE; break; } case VINDEXED: { freereg(fs, e->u.s.aux); freereg(fs, e->u.s.info); e->u.s.info = luaK_codeABC(fs, OP_GETTABLE, 0, e->u.s.info, e->u.s.aux); e->k = VRELOCABLE; break; } case VVARARG: case VCALL: { luaK_setoneret(fs, e); break; } default: break; /* there is one value available (somewhere) */ } } static int code_label (FuncState *fs, int A, int b, int jump) { luaK_getlabel(fs); /* those instructions may be jump targets */ return luaK_codeABC(fs, OP_LOADBOOL, A, b, jump); } static void discharge2reg (FuncState *fs, expdesc *e, int reg) { luaK_dischargevars(fs, e); switch (e->k) { case VNIL: { luaK_nil(fs, reg, 1); break; } case VFALSE: case VTRUE: { luaK_codeABC(fs, OP_LOADBOOL, reg, e->k == VTRUE, 0); break; } case VK: { luaK_codeABx(fs, OP_LOADK, reg, e->u.s.info); break; } case VKNUM: { luaK_codeABx(fs, OP_LOADK, reg, luaK_numberK(fs, e->u.nval)); break; } case VRELOCABLE: { Instruction *pc = &getcode(fs, e); SETARG_A(*pc, reg); break; } case VNONRELOC: { if (reg != e->u.s.info) luaK_codeABC(fs, OP_MOVE, reg, e->u.s.info, 0); break; } default: { lua_assert(e->k == VVOID || e->k == VJMP); return; /* nothing to do... */ } } e->u.s.info = reg; e->k = VNONRELOC; } static void discharge2anyreg (FuncState *fs, expdesc *e) { if (e->k != VNONRELOC) { luaK_reserveregs(fs, 1); discharge2reg(fs, e, fs->freereg-1); } } static void exp2reg (FuncState *fs, expdesc *e, int reg) { discharge2reg(fs, e, reg); if (e->k == VJMP) luaK_concat(fs, &e->t, e->u.s.info); /* put this jump in `t' list */ if (hasjumps(e)) { int final; /* position after whole expression */ int p_f = NO_JUMP; /* position of an eventual LOAD false */ int p_t = NO_JUMP; /* position of an eventual LOAD true */ if (need_value(fs, e->t) || need_value(fs, e->f)) { int fj = (e->k == VJMP) ? NO_JUMP : luaK_jump(fs); p_f = code_label(fs, reg, 0, 1); p_t = code_label(fs, reg, 1, 0); luaK_patchtohere(fs, fj); } final = luaK_getlabel(fs); patchlistaux(fs, e->f, final, reg, p_f); patchlistaux(fs, e->t, final, reg, p_t); } e->f = e->t = NO_JUMP; e->u.s.info = reg; e->k = VNONRELOC; } void luaK_exp2nextreg (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); freeexp(fs, e); luaK_reserveregs(fs, 1); exp2reg(fs, e, fs->freereg - 1); } int luaK_exp2anyreg (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); if (e->k == VNONRELOC) { if (!hasjumps(e)) return e->u.s.info; /* exp is already in a register */ if (e->u.s.info >= fs->nactvar) { /* reg. is not a local? */ exp2reg(fs, e, e->u.s.info); /* put value on it */ return e->u.s.info; } } luaK_exp2nextreg(fs, e); /* default */ return e->u.s.info; } void luaK_exp2val (FuncState *fs, expdesc *e) { if (hasjumps(e)) luaK_exp2anyreg(fs, e); else luaK_dischargevars(fs, e); } int luaK_exp2RK (FuncState *fs, expdesc *e) { luaK_exp2val(fs, e); switch (e->k) { case VKNUM: case VTRUE: case VFALSE: case VNIL: { if (fs->nk <= MAXINDEXRK) { /* constant fit in RK operand? */ e->u.s.info = (e->k == VNIL) ? nilK(fs) : (e->k == VKNUM) ? luaK_numberK(fs, e->u.nval) : boolK(fs, (e->k == VTRUE)); e->k = VK; return RKASK(e->u.s.info); } else break; } case VK: { if (e->u.s.info <= MAXINDEXRK) /* constant fit in argC? */ return RKASK(e->u.s.info); else break; } default: break; } /* not a constant in the right range: put it in a register */ return luaK_exp2anyreg(fs, e); } void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) { switch (var->k) { case VLOCAL: { freeexp(fs, ex); exp2reg(fs, ex, var->u.s.info); return; } case VUPVAL: { int e = luaK_exp2anyreg(fs, ex); luaK_codeABC(fs, OP_SETUPVAL, e, var->u.s.info, 0); break; } case VGLOBAL: { int e = luaK_exp2anyreg(fs, ex); luaK_codeABx(fs, OP_SETGLOBAL, e, var->u.s.info); break; } case VINDEXED: { int e = luaK_exp2RK(fs, ex); luaK_codeABC(fs, OP_SETTABLE, var->u.s.info, var->u.s.aux, e); break; } default: { lua_assert(0); /* invalid var kind to store */ break; } } freeexp(fs, ex); } void luaK_self (FuncState *fs, expdesc *e, expdesc *key) { int func; luaK_exp2anyreg(fs, e); freeexp(fs, e); func = fs->freereg; luaK_reserveregs(fs, 2); luaK_codeABC(fs, OP_SELF, func, e->u.s.info, luaK_exp2RK(fs, key)); freeexp(fs, key); e->u.s.info = func; e->k = VNONRELOC; } static void invertjump (FuncState *fs, expdesc *e) { Instruction *pc = getjumpcontrol(fs, e->u.s.info); lua_assert(testTMode(GET_OPCODE(*pc)) && GET_OPCODE(*pc) != OP_TESTSET && GET_OPCODE(*pc) != OP_TEST); SETARG_A(*pc, !(GETARG_A(*pc))); } static int jumponcond (FuncState *fs, expdesc *e, int cond) { if (e->k == VRELOCABLE) { Instruction ie = getcode(fs, e); if (GET_OPCODE(ie) == OP_NOT) { fs->pc--; /* remove previous OP_NOT */ return condjump(fs, OP_TEST, GETARG_B(ie), 0, !cond); } /* else go through */ } discharge2anyreg(fs, e); freeexp(fs, e); return condjump(fs, OP_TESTSET, NO_REG, e->u.s.info, cond); } void luaK_goiftrue (FuncState *fs, expdesc *e) { int pc; /* pc of last jump */ luaK_dischargevars(fs, e); switch (e->k) { case VK: case VKNUM: case VTRUE: { pc = NO_JUMP; /* always true; do nothing */ break; } case VJMP: { invertjump(fs, e); pc = e->u.s.info; break; } default: { pc = jumponcond(fs, e, 0); break; } } luaK_concat(fs, &e->f, pc); /* insert last jump in `f' list */ luaK_patchtohere(fs, e->t); e->t = NO_JUMP; } static void luaK_goiffalse (FuncState *fs, expdesc *e) { int pc; /* pc of last jump */ luaK_dischargevars(fs, e); switch (e->k) { case VNIL: case VFALSE: { pc = NO_JUMP; /* always false; do nothing */ break; } case VJMP: { pc = e->u.s.info; break; } default: { pc = jumponcond(fs, e, 1); break; } } luaK_concat(fs, &e->t, pc); /* insert last jump in `t' list */ luaK_patchtohere(fs, e->f); e->f = NO_JUMP; } static void codenot (FuncState *fs, expdesc *e) { luaK_dischargevars(fs, e); switch (e->k) { case VNIL: case VFALSE: { e->k = VTRUE; break; } case VK: case VKNUM: case VTRUE: { e->k = VFALSE; break; } case VJMP: { invertjump(fs, e); break; } case VRELOCABLE: case VNONRELOC: { discharge2anyreg(fs, e); freeexp(fs, e); e->u.s.info = luaK_codeABC(fs, OP_NOT, 0, e->u.s.info, 0); e->k = VRELOCABLE; break; } default: { lua_assert(0); /* cannot happen */ break; } } /* interchange true and false lists */ { int temp = e->f; e->f = e->t; e->t = temp; } removevalues(fs, e->f); removevalues(fs, e->t); } void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { t->u.s.aux = luaK_exp2RK(fs, k); t->k = VINDEXED; } static int constfolding (OpCode op, expdesc *e1, expdesc *e2) { lua_Number v1, v2, r; if (!isnumeral(e1) || !isnumeral(e2)) return 0; v1 = e1->u.nval; v2 = e2->u.nval; switch (op) { case OP_ADD: r = luai_numadd(v1, v2); break; case OP_SUB: r = luai_numsub(v1, v2); break; case OP_MUL: r = luai_nummul(v1, v2); break; case OP_DIV: if (v2 == 0) return 0; /* do not attempt to divide by 0 */ r = luai_numdiv(v1, v2); break; case OP_MOD: if (v2 == 0) return 0; /* do not attempt to divide by 0 */ r = luai_nummod(v1, v2); break; case OP_POW: r = luai_numpow(v1, v2); break; case OP_UNM: r = luai_numunm(v1); break; case OP_LEN: return 0; /* no constant folding for 'len' */ default: lua_assert(0); r = 0; break; } if (luai_numisnan(r)) return 0; /* do not attempt to produce NaN */ e1->u.nval = r; return 1; } static void codearith (FuncState *fs, OpCode op, expdesc *e1, expdesc *e2) { if (constfolding(op, e1, e2)) return; else { int o2 = (op != OP_UNM && op != OP_LEN) ? luaK_exp2RK(fs, e2) : 0; int o1 = luaK_exp2RK(fs, e1); if (o1 > o2) { freeexp(fs, e1); freeexp(fs, e2); } else { freeexp(fs, e2); freeexp(fs, e1); } e1->u.s.info = luaK_codeABC(fs, op, 0, o1, o2); e1->k = VRELOCABLE; } } static void codecomp (FuncState *fs, OpCode op, int cond, expdesc *e1, expdesc *e2) { int o1 = luaK_exp2RK(fs, e1); int o2 = luaK_exp2RK(fs, e2); freeexp(fs, e2); freeexp(fs, e1); if (cond == 0 && op != OP_EQ) { int temp; /* exchange args to replace by `<' or `<=' */ temp = o1; o1 = o2; o2 = temp; /* o1 <==> o2 */ cond = 1; } e1->u.s.info = condjump(fs, op, cond, o1, o2); e1->k = VJMP; } void luaK_prefix (FuncState *fs, UnOpr op, expdesc *e) { expdesc e2; e2.t = e2.f = NO_JUMP; e2.k = VKNUM; e2.u.nval = 0; switch (op) { case OPR_MINUS: { if (!isnumeral(e)) luaK_exp2anyreg(fs, e); /* cannot operate on non-numeric constants */ codearith(fs, OP_UNM, e, &e2); break; } case OPR_NOT: codenot(fs, e); break; case OPR_LEN: { luaK_exp2anyreg(fs, e); /* cannot operate on constants */ codearith(fs, OP_LEN, e, &e2); break; } default: lua_assert(0); } } void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) { switch (op) { case OPR_AND: { luaK_goiftrue(fs, v); break; } case OPR_OR: { luaK_goiffalse(fs, v); break; } case OPR_CONCAT: { luaK_exp2nextreg(fs, v); /* operand must be on the `stack' */ break; } case OPR_ADD: case OPR_SUB: case OPR_MUL: case OPR_DIV: case OPR_MOD: case OPR_POW: { if (!isnumeral(v)) luaK_exp2RK(fs, v); break; } default: { luaK_exp2RK(fs, v); break; } } } void luaK_posfix (FuncState *fs, BinOpr op, expdesc *e1, expdesc *e2) { switch (op) { case OPR_AND: { lua_assert(e1->t == NO_JUMP); /* list must be closed */ luaK_dischargevars(fs, e2); luaK_concat(fs, &e2->f, e1->f); *e1 = *e2; break; } case OPR_OR: { lua_assert(e1->f == NO_JUMP); /* list must be closed */ luaK_dischargevars(fs, e2); luaK_concat(fs, &e2->t, e1->t); *e1 = *e2; break; } case OPR_CONCAT: { luaK_exp2val(fs, e2); if (e2->k == VRELOCABLE && GET_OPCODE(getcode(fs, e2)) == OP_CONCAT) { lua_assert(e1->u.s.info == GETARG_B(getcode(fs, e2))-1); freeexp(fs, e1); SETARG_B(getcode(fs, e2), e1->u.s.info); e1->k = VRELOCABLE; e1->u.s.info = e2->u.s.info; } else { luaK_exp2nextreg(fs, e2); /* operand must be on the 'stack' */ codearith(fs, OP_CONCAT, e1, e2); } break; } case OPR_ADD: codearith(fs, OP_ADD, e1, e2); break; case OPR_SUB: codearith(fs, OP_SUB, e1, e2); break; case OPR_MUL: codearith(fs, OP_MUL, e1, e2); break; case OPR_DIV: codearith(fs, OP_DIV, e1, e2); break; case OPR_MOD: codearith(fs, OP_MOD, e1, e2); break; case OPR_POW: codearith(fs, OP_POW, e1, e2); break; case OPR_EQ: codecomp(fs, OP_EQ, 1, e1, e2); break; case OPR_NE: codecomp(fs, OP_EQ, 0, e1, e2); break; case OPR_LT: codecomp(fs, OP_LT, 1, e1, e2); break; case OPR_LE: codecomp(fs, OP_LE, 1, e1, e2); break; case OPR_GT: codecomp(fs, OP_LT, 0, e1, e2); break; case OPR_GE: codecomp(fs, OP_LE, 0, e1, e2); break; default: lua_assert(0); } } void luaK_fixline (FuncState *fs, int line) { fs->f->lineinfo[fs->pc - 1] = line; } static int luaK_code (FuncState *fs, Instruction i, int line) { Proto *f = fs->f; dischargejpc(fs); /* `pc' will change */ /* put new instruction in code array */ luaM_growvector(fs->L, f->code, fs->pc, f->sizecode, Instruction, MAX_INT, "code size overflow"); f->code[fs->pc] = i; /* save corresponding line information */ luaM_growvector(fs->L, f->lineinfo, fs->pc, f->sizelineinfo, int, MAX_INT, "code size overflow"); f->lineinfo[fs->pc] = line; return fs->pc++; } int luaK_codeABC (FuncState *fs, OpCode o, int a, int b, int c) { lua_assert(getOpMode(o) == iABC); lua_assert(getBMode(o) != OpArgN || b == 0); lua_assert(getCMode(o) != OpArgN || c == 0); return luaK_code(fs, CREATE_ABC(o, a, b, c), fs->ls->lastline); } int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) { lua_assert(getOpMode(o) == iABx || getOpMode(o) == iAsBx); lua_assert(getCMode(o) == OpArgN); return luaK_code(fs, CREATE_ABx(o, a, bc), fs->ls->lastline); } void luaK_setlist (FuncState *fs, int base, int nelems, int tostore) { int c = (nelems - 1)/LFIELDS_PER_FLUSH + 1; int b = (tostore == LUA_MULTRET) ? 0 : tostore; lua_assert(tostore != 0); if (c <= MAXARG_C) luaK_codeABC(fs, OP_SETLIST, base, b, c); else { luaK_codeABC(fs, OP_SETLIST, base, b, 0); luaK_code(fs, cast(Instruction, c), fs->ls->lastline); } fs->freereg = base + 1; /* free registers with list values */ } ================================================ FILE: src/lcode.h ================================================ /* ** $Id: lcode.h,v 1.48.1.1 2007/12/27 13:02:25 roberto Exp $ ** Code generator for Lua ** See Copyright Notice in lua.h */ #ifndef lcode_h #define lcode_h #include "llex.h" #include "lobject.h" #include "lopcodes.h" #include "lparser.h" /* ** Marks the end of a patch list. It is an invalid value both as an absolute ** address, and as a list link (would link an element to itself). */ #define NO_JUMP (-1) /* ** grep "ORDER OPR" if you change these enums */ typedef enum BinOpr { OPR_ADD, OPR_SUB, OPR_MUL, OPR_DIV, OPR_MOD, OPR_POW, OPR_CONCAT, OPR_NE, OPR_EQ, OPR_LT, OPR_LE, OPR_GT, OPR_GE, OPR_AND, OPR_OR, OPR_NOBINOPR } BinOpr; typedef enum UnOpr { OPR_MINUS, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr; #define getcode(fs,e) ((fs)->f->code[(e)->u.s.info]) #define luaK_codeAsBx(fs,o,A,sBx) luaK_codeABx(fs,o,A,(sBx)+MAXARG_sBx) #define luaK_setmultret(fs,e) luaK_setreturns(fs, e, LUA_MULTRET) LUAI_FUNC int luaK_codeABx (FuncState *fs, OpCode o, int A, unsigned int Bx); LUAI_FUNC int luaK_codeABC (FuncState *fs, OpCode o, int A, int B, int C); LUAI_FUNC void luaK_fixline (FuncState *fs, int line); LUAI_FUNC void luaK_nil (FuncState *fs, int from, int n); LUAI_FUNC void luaK_reserveregs (FuncState *fs, int n); LUAI_FUNC void luaK_checkstack (FuncState *fs, int n); LUAI_FUNC int luaK_stringK (FuncState *fs, TString *s); LUAI_FUNC int luaK_numberK (FuncState *fs, lua_Number r); LUAI_FUNC void luaK_dischargevars (FuncState *fs, expdesc *e); LUAI_FUNC int luaK_exp2anyreg (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_exp2nextreg (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_exp2val (FuncState *fs, expdesc *e); LUAI_FUNC int luaK_exp2RK (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_self (FuncState *fs, expdesc *e, expdesc *key); LUAI_FUNC void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k); LUAI_FUNC void luaK_goiftrue (FuncState *fs, expdesc *e); LUAI_FUNC void luaK_storevar (FuncState *fs, expdesc *var, expdesc *e); LUAI_FUNC void luaK_setreturns (FuncState *fs, expdesc *e, int nresults); LUAI_FUNC void luaK_setoneret (FuncState *fs, expdesc *e); LUAI_FUNC int luaK_jump (FuncState *fs); LUAI_FUNC void luaK_ret (FuncState *fs, int first, int nret); LUAI_FUNC void luaK_patchlist (FuncState *fs, int list, int target); LUAI_FUNC void luaK_patchtohere (FuncState *fs, int list); LUAI_FUNC void luaK_concat (FuncState *fs, int *l1, int l2); LUAI_FUNC int luaK_getlabel (FuncState *fs); LUAI_FUNC void luaK_prefix (FuncState *fs, UnOpr op, expdesc *v); LUAI_FUNC void luaK_infix (FuncState *fs, BinOpr op, expdesc *v); LUAI_FUNC void luaK_posfix (FuncState *fs, BinOpr op, expdesc *v1, expdesc *v2); LUAI_FUNC void luaK_setlist (FuncState *fs, int base, int nelems, int tostore); #endif ================================================ FILE: src/lcurses/Makefile ================================================ curses.o: curses.c window.c chstr.c _helpers.c compat-5.2.c compat-5.2.h strlcpy.c ================================================ FILE: src/lcurses/_helpers.c ================================================ /* * POSIX library for Lua 5.1, 5.2 & 5.3. * (c) Gary V. Vaughan , 2013-2017 * (c) Reuben Thomas 2010-2013 * (c) Natanael Copa 2008-2010 * Clean up and bug fixes by Leo Razoumov 2006-10-11 * Luiz Henrique de Figueiredo 07 Apr 2006 23:17:49 * Based on original by Claudio Terra for Lua 3.x. * With contributions by Roberto Ierusalimschy. * With documentation from Steve Donovan 2012 */ #ifndef LCURSES__HELPERS_C #define LCURSES__HELPERS_C 1 #include #include #include #include #include #include #include /* for _POSIX_VERSION */ #ifdef __NetBSD__ #include #else #include #endif #include #include "../lua.h" #include "../lualib.h" #include "../lauxlib.h" #if LUA_VERSION_NUM < 503 # define lua_isinteger lua_isnumber # if LUA_VERSION_NUM == 501 # include "compat-5.2.c" # endif #endif #if LUA_VERSION_NUM == 502 || LUA_VERSION_NUM == 503 # define lua_objlen lua_rawlen # define lua_strlen lua_rawlen # define luaL_openlib(L,n,l,nup) luaL_setfuncs((L),(l),(nup)) # define luaL_register(L,n,l) (luaL_newlib(L,l)) #endif #ifndef STREQ # define STREQ(a, b) (strcmp (a, b) == 0) #endif /* Mark unused parameters required only to match a function type specification. */ #ifdef __GNUC__ # define LCURSES_UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) #else # define LCURSES_UNUSED(x) UNUSED_ ## x #endif /* LCURSES_STMT_BEG/END are used to create macros that expand to a single compound statement in a portable way. */ #if defined __GNUC__ && !defined __STRICT_ANSI__ && !defined __cplusplus # define LCURSES_STMT_BEG (void)( # define LCURSES_STMT_END ) #else # if (defined sun || defined __sun__) # define LCURSES_STMT_BEG if (1) # define LCURSES_STMT_END else (void)0 # else # define LCURSES_STMT_BEG do # define LCURSES_STMT_END while (0) # endif #endif /* The extra indirection to these macros is required so that if the arguments are themselves macros, they will get expanded too. */ #define LCURSES__SPLICE(_s, _t) _s##_t #define LCURSES_SPLICE(_s, _t) LCURSES__SPLICE(_s, _t) #define LCURSES__STR(_s) #_s #define LCURSES_STR(_s) LCURSES__STR(_s) /* The +1 is to step over the leading '_' that is required to prevent premature expansion of MENTRY arguments if we didn't add it. */ #define LCURSES__STR_1(_s) (#_s + 1) #define LCURSES_STR_1(_s) LCURSES__STR_1(_s) #define LCURSES_CONST(_f) LCURSES_STMT_BEG { \ lua_pushinteger(L, _f); \ lua_setfield(L, -2, #_f); \ } LCURSES_STMT_END #define LCURSES_FUNC(_s) {LCURSES_STR_1(_s), (_s)} #define pushokresult(b) pushboolresult((int) (b) == OK) #ifndef errno extern int errno; #endif /* ========================= * * Bad argument diagnostics. * * ========================= */ static int argtypeerror(lua_State *L, int narg, const char *expected) { const char *got = luaL_typename(L, narg); return luaL_argerror(L, narg, lua_pushfstring(L, "%s expected, got %s", expected, got)); } static lua_Integer checkinteger(lua_State *L, int narg, const char *expected) { lua_Integer d = lua_tointeger(L, narg); if (d == 0 && !lua_isinteger(L, narg)) argtypeerror(L, narg, expected); return d; } static int checkint(lua_State *L, int narg) { return (int)checkinteger(L, narg, "int"); } static chtype checkch(lua_State *L, int narg) { if (lua_isnumber(L, narg)) return (chtype)checkint(L, narg); if (lua_isstring(L, narg)) return *lua_tostring(L, narg); return argtypeerror(L, narg, "int or char"); } static chtype optch(lua_State *L, int narg, chtype def) { if (lua_isnoneornil(L, narg)) return def; if (lua_isnumber(L, narg) || lua_isstring(L, narg)) return checkch(L, narg); return argtypeerror(L, narg, "int or char or nil"); } static int optint(lua_State *L, int narg, lua_Integer def) { if (lua_isnoneornil(L, narg)) return (int) def; return (int)checkinteger(L, narg, "int or nil"); } #define pushboolresult(b) (lua_pushboolean(L, (b)), 1) #define pushintresult(n) (lua_pushinteger(L, (n)), 1) #define pushstringresult(s) (lua_pushstring(L, (s)), 1) /* ================== * * Utility functions. * * ================== */ #define pushintegerfield(k,v) LCURSES_STMT_BEG { \ lua_pushinteger(L, (lua_Integer) v); lua_setfield(L, -2, k); \ } LCURSES_STMT_END #define pushnumberfield(k,v) LCURSES_STMT_BEG { \ lua_pushnumber(L, (lua_Number) v); lua_setfield(L, -2, k); \ } LCURSES_STMT_END #define pushstringfield(k,v) LCURSES_STMT_BEG { \ if (v) { \ lua_pushstring(L, (const char *) v); \ lua_setfield(L, -2, k); \ } \ } LCURSES_STMT_END #define pushliteralfield(k,v) LCURSES_STMT_BEG { \ if (v) { \ lua_pushliteral(L, v); \ lua_setfield(L, -2, k); \ } \ } LCURSES_STMT_END #define settypemetatable(t) LCURSES_STMT_BEG { \ if (luaL_newmetatable(L, t) == 1) \ pushliteralfield("_type", t); \ lua_setmetatable(L, -2); \ } LCURSES_STMT_END #define setintegerfield(_p, _n) pushintegerfield(LCURSES_STR(_n), _p->_n) #define setnumberfield(_p, _n) pushnumberfield(LCURSES_STR(_n), _p->_n) #define setstringfield(_p, _n) pushstringfield(LCURSES_STR(_n), _p->_n) #endif /*LCURSES__HELPERS_C*/ ================================================ FILE: src/lcurses/chstr.c ================================================ /* * Curses binding for Lua 5.1, 5.2 & 5.3. * * (c) Gary V. Vaughan 2013-2017 * (c) Reuben Thomas 2009-2012 * (c) Tiago Dionizio 2004-2007 * * 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. */ /*** Curses attributed string buffers. An array of characters, plus associated curses attributes and colors at each position. Although marginally useful alone, the constants used to set colors and attributes in `chstr` buffers are not defined until **after** `curses.initscr ()` has been called. @classmod curses.chstr */ #ifndef LCURSES_CHSTR_C #define LCURSES_CHSTR_C 1 #include "_helpers.c" static const char *CHSTRMETA = "curses:chstr"; typedef struct { unsigned int len; chtype str[1]; } chstr; #define CHSTR_SIZE(len) (sizeof(chstr) + len * sizeof(chtype)) /* create new chstr object and leave it in the lua stack */ static chstr * chstr_new(lua_State *L, int len) { chstr *cs; if (len < 1) return luaL_error(L, "invalid chstr length"), NULL; cs = lua_newuserdata(L, CHSTR_SIZE(len)); luaL_getmetatable(L, CHSTRMETA); lua_setmetatable(L, -2); cs->len = len; return cs; } /* get chstr from lua (convert if needed) */ static chstr * checkchstr(lua_State *L, int narg) { chstr *cs = (chstr*)luaL_checkudata(L, narg, CHSTRMETA); if (cs) return cs; luaL_argerror(L, narg, "bad curses chstr"); /*NOTREACHED*/ return NULL; } /*** Change the contents of the chstr. @function set_str @int o offset to start of change @string s characters to insert into *cs* at *o* @int[opt=A_NORMAL] attr attributes for changed elements @int[opt=1] rep repeat count @usage cs = curses.chstr (10) cs:set_str(0, "0123456789", curses.A_BOLD) */ static int Cset_str(lua_State *L) { chstr *cs = checkchstr(L, 1); int offset = checkint(L, 2); const char *str = luaL_checkstring(L, 3); int len = lua_strlen(L, 3); int attr = optint(L, 4, A_NORMAL); int rep = optint(L, 5, 1); int i; if (offset < 0) return 0; while (rep-- > 0 && offset <= (int)cs->len) { if (offset + len - 1 > (int)cs->len) len = cs->len - offset + 1; for (i = 0; i < len; ++i) cs->str[offset + i] = str[i] | attr; offset += len; } return 0; } /*** Set a character in the buffer. *ch* can be a one-character string, or an integer from `string.byte` @function set_ch @int o offset to start of change @param int|string ch character to insert @int[opt=A_NORMAL] attr attributes for changed elements @int[opt=1] rep repeat count @usage -- Write a bold 'A' followed by normal 'a' chars to a new buffer size = 10 cs = curses.chstr (size) cs:set_ch(0, 'A', curses.A_BOLD) cs:set_ch(1, 'a', curses.A_NORMAL, size - 1) */ static int Cset_ch(lua_State *L) { chstr* cs = checkchstr(L, 1); int offset = checkint(L, 2); chtype ch = checkch(L, 3); int attr = optint(L, 4, A_NORMAL); int rep = optint(L, 5, 1); while (rep-- > 0) { if (offset < 0 || offset >= (int) cs->len) return 0; cs->str[offset] = ch | attr; ++offset; } return 0; } /*** Get information from the chstr. @function get @int o offset from start of *cs* @treturn int character at offset *o* in *cs* @treturn int bitwise-OR of attributes at offset *o* in *cs* @treturn int colorpair at offset *o* in *cs* @usage cs = curses.chstr (10) cs:set_ch(0, 'A', curses.A_BOLD, 10) --> 65 2097152 0 print (cs:get (9)) */ static int Cget(lua_State *L) { chstr* cs = checkchstr(L, 1); int offset = checkint(L, 2); chtype ch; if (offset < 0 || offset >= (int) cs->len) return 0; ch = cs->str[offset]; lua_pushinteger(L, ch & A_CHARTEXT); lua_pushinteger(L, ch & A_ATTRIBUTES); lua_pushinteger(L, ch & A_COLOR); return 3; } /*** Retrieve chstr length. @function len @tparam chstr cs buffer to act on @treturn int length of *cs* @usage cs = curses.chstr (123) --> 123 print (cs:len ()) */ static int Clen(lua_State *L) { chstr *cs = checkchstr(L, 1); return pushintresult(cs->len); } /*** Duplicate chstr. @function dup @treturn chstr duplicate of *cs* @usage dup = cs:dup () */ static int Cdup(lua_State *L) { chstr *cs = checkchstr(L, 1); chstr *ncs = chstr_new(L, cs->len); memcpy(ncs->str, cs->str, CHSTR_SIZE(cs->len)); return 1; } /*** Initialise a new chstr. @function __call @int len buffer length @treturn chstr a new chstr filled with spaces @usage cs = curses.chstr (10) */ static int C__call(lua_State *L) { int len = checkint(L, 2); chstr* ncs = chstr_new(L, len); memset(ncs->str, ' ', len * sizeof(chtype)); return 1; } static const luaL_Reg curses_chstr_fns[] = { LCURSES_FUNC( Clen ), LCURSES_FUNC( Cset_ch ), LCURSES_FUNC( Cset_str ), LCURSES_FUNC( Cget ), LCURSES_FUNC( Cdup ), { NULL, NULL } }; LUALIB_API int luaopen_curses_chstr(lua_State *L) { int t, mt; luaL_register(L, "curses.chstr", curses_chstr_fns); t = lua_gettop(L); lua_createtable(L, 0, 1); /* u = {} */ lua_pushcfunction(L, C__call); lua_setfield(L, -2, "__call"); /* u.__call = C__call */ lua_setmetatable(L, -2); /* setmetatable (t, u) */ luaL_newmetatable(L, CHSTRMETA); mt = lua_gettop(L); lua_pushvalue(L, mt); lua_setfield(L, -2, "__index"); /* mt.__index = mt */ lua_pushliteral(L, "CursesChstr"); lua_setfield(L, -2, "_type"); /* mt._type = "CursesChstr" */ /* for k,v in pairs(t) do mt[k]=v end */ for (lua_pushnil(L); lua_next(L, t) != 0;) lua_setfield(L, mt, lua_tostring(L, -2)); lua_pop(L, 1); /* pop mt */ return 1; } #endif /*!LCURSES_CHSTR_C*/ ================================================ FILE: src/lcurses/compat-5.2.c ================================================ #include #include #include "../lua.h" #include "../lauxlib.h" #include "compat-5.2.h" #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM == 501 int lua_absindex (lua_State *L, int i) { if (i < 0 && i > LUA_REGISTRYINDEX) i += lua_gettop(L) + 1; return i; } void lua_copy (lua_State *L, int from, int to) { int abs_to = lua_absindex(L, to); luaL_checkstack(L, 1, "not enough stack slots"); lua_pushvalue(L, from); lua_replace(L, abs_to); } void lua_rawgetp (lua_State *L, int i, const void *p) { int abs_i = lua_absindex(L, i); lua_pushlightuserdata(L, (void*)p); lua_rawget(L, abs_i); } void lua_rawsetp (lua_State *L, int i, const void *p) { int abs_i = lua_absindex(L, i); luaL_checkstack(L, 1, "not enough stack slots"); lua_pushlightuserdata(L, (void*)p); lua_insert(L, -2); lua_rawset(L, abs_i); } void *luaL_testudata (lua_State *L, int i, const char *tname) { void *p = lua_touserdata(L, i); luaL_checkstack(L, 2, "not enough stack slots"); if (p == NULL || !lua_getmetatable(L, i)) return NULL; else { int res = 0; luaL_getmetatable(L, tname); res = lua_rawequal(L, -1, -2); lua_pop(L, 2); if (!res) p = NULL; } return p; } lua_Number lua_tonumberx (lua_State *L, int i, int *isnum) { lua_Number n = lua_tonumber(L, i); if (isnum != NULL) { *isnum = (n != 0 || lua_isnumber(L, i)); } return n; } #define PACKAGE_KEY "_COMPAT52_PACKAGE" static void push_package_table (lua_State *L) { lua_pushliteral(L, PACKAGE_KEY); lua_rawget(L, LUA_REGISTRYINDEX); if (!lua_istable(L, -1)) { lua_pop(L, 1); /* try to get package table from globals */ lua_pushliteral(L, "package"); lua_rawget(L, LUA_GLOBALSINDEX); if (lua_istable(L, -1)) { lua_pushliteral(L, PACKAGE_KEY); lua_pushvalue(L, -2); lua_rawset(L, LUA_REGISTRYINDEX); } } } void lua_getuservalue (lua_State *L, int i) { luaL_checktype(L, i, LUA_TUSERDATA); luaL_checkstack(L, 2, "not enough stack slots"); lua_getfenv(L, i); lua_pushvalue(L, LUA_GLOBALSINDEX); if (lua_rawequal(L, -1, -2)) { lua_pop(L, 1); lua_pushnil(L); lua_replace(L, -2); } else { lua_pop(L, 1); push_package_table(L); if (lua_rawequal(L, -1, -2)) { lua_pop(L, 1); lua_pushnil(L); lua_replace(L, -2); } else lua_pop(L, 1); } } void lua_setuservalue (lua_State *L, int i) { luaL_checktype(L, i, LUA_TUSERDATA); if (lua_isnil(L, -1)) { luaL_checkstack(L, 1, "not enough stack slots"); lua_pushvalue(L, LUA_GLOBALSINDEX); lua_replace(L, -2); } lua_setfenv(L, i); } /* ** Adapted from Lua 5.2.0 */ void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { luaL_checkstack(L, nup+1, "too many upvalues"); for (; l->name != NULL; l++) { /* fill the table with given functions */ int i; lua_pushstring(L, l->name); for (i = 0; i < nup; i++) /* copy upvalues to the top */ lua_pushvalue(L, -(nup + 1)); lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ lua_settable(L, -(nup + 3)); /* table must be below the upvalues, the name and the closure */ } lua_pop(L, nup); /* remove upvalues */ } void luaL_setmetatable (lua_State *L, const char *tname) { luaL_checkstack(L, 1, "not enough stack slots"); luaL_getmetatable(L, tname); lua_setmetatable(L, -2); } int luaL_getsubtable (lua_State *L, int i, const char *name) { int abs_i = lua_absindex(L, i); luaL_checkstack(L, 3, "not enough stack slots"); lua_pushstring(L, name); lua_gettable(L, abs_i); if (lua_istable(L, -1)) return 1; lua_pop(L, 1); lua_newtable(L); lua_pushstring(L, name); lua_pushvalue(L, -2); lua_settable(L, abs_i); return 0; } #if !defined(COMPAT52_IS_LUAJIT) static int countlevels (lua_State *L) { lua_Debug ar; int li = 1, le = 1; /* find an upper bound */ while (lua_getstack(L, le, &ar)) { li = le; le *= 2; } /* do a binary search */ while (li < le) { int m = (li + le)/2; if (lua_getstack(L, m, &ar)) li = m + 1; else le = m; } return le - 1; } static int findfield (lua_State *L, int objidx, int level) { if (level == 0 || !lua_istable(L, -1)) return 0; /* not found */ lua_pushnil(L); /* start 'next' loop */ while (lua_next(L, -2)) { /* for each pair in table */ if (lua_type(L, -2) == LUA_TSTRING) { /* ignore non-string keys */ if (lua_rawequal(L, objidx, -1)) { /* found object? */ lua_pop(L, 1); /* remove value (but keep name) */ return 1; } else if (findfield(L, objidx, level - 1)) { /* try recursively */ lua_remove(L, -2); /* remove table (but keep name) */ lua_pushliteral(L, "."); lua_insert(L, -2); /* place '.' between the two names */ lua_concat(L, 3); return 1; } } lua_pop(L, 1); /* remove value */ } return 0; /* not found */ } static int pushglobalfuncname (lua_State *L, lua_Debug *ar) { int top = lua_gettop(L); lua_getinfo(L, "f", ar); /* push function */ lua_pushvalue(L, LUA_GLOBALSINDEX); if (findfield(L, top + 1, 2)) { lua_copy(L, -1, top + 1); /* move name to proper place */ lua_pop(L, 2); /* remove pushed values */ return 1; } else { lua_settop(L, top); /* remove function and global table */ return 0; } } static void pushfuncname (lua_State *L, lua_Debug *ar) { if (*ar->namewhat != '\0') /* is there a name? */ lua_pushfstring(L, "function " LUA_QS, ar->name); else if (*ar->what == 'm') /* main? */ lua_pushliteral(L, "main chunk"); else if (*ar->what == 'C') { if (pushglobalfuncname(L, ar)) { lua_pushfstring(L, "function " LUA_QS, lua_tostring(L, -1)); lua_remove(L, -2); /* remove name */ } else lua_pushliteral(L, "?"); } else lua_pushfstring(L, "function <%s:%d>", ar->short_src, ar->linedefined); } #define LEVELS1 12 /* size of the first part of the stack */ #define LEVELS2 10 /* size of the second part of the stack */ void luaL_traceback (lua_State *L, lua_State *L1, const char *msg, int level) { lua_Debug ar; int top = lua_gettop(L); int numlevels = countlevels(L1); int mark = (numlevels > LEVELS1 + LEVELS2) ? LEVELS1 : 0; if (msg) lua_pushfstring(L, "%s\n", msg); lua_pushliteral(L, "stack traceback:"); while (lua_getstack(L1, level++, &ar)) { if (level == mark) { /* too many levels? */ lua_pushliteral(L, "\n\t..."); /* add a '...' */ level = numlevels - LEVELS2; /* and skip to last ones */ } else { lua_getinfo(L1, "Slnt", &ar); lua_pushfstring(L, "\n\t%s:", ar.short_src); if (ar.currentline > 0) lua_pushfstring(L, "%d:", ar.currentline); lua_pushliteral(L, " in "); pushfuncname(L, &ar); lua_concat(L, lua_gettop(L) - top); } } lua_concat(L, lua_gettop(L) - top); } #endif void luaL_checkversion (lua_State *L) { (void)L; } #if !defined(COMPAT52_IS_LUAJIT) int luaL_fileresult (lua_State *L, int stat, const char *fname) { int en = errno; /* calls to Lua API may change this value */ if (stat) { lua_pushboolean(L, 1); return 1; } else { lua_pushnil(L); if (fname) lua_pushfstring(L, "%s: %s", fname, strerror(en)); else lua_pushstring(L, strerror(en)); lua_pushnumber(L, (lua_Number)en); return 3; } } #endif #endif /* Lua 5.0 or Lua 5.1 */ #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM == 501 #include typedef LUAI_INT32 LUA_INT32; /********************************************************************/ /* extract of 5.2's luaconf.h */ /* detects proper defines for faster unsigned<->number conversion */ /* see copyright notice at the end of this file */ /********************************************************************/ #if !defined(LUA_ANSI) && defined(_WIN32) && !defined(_WIN32_WCE) #define LUA_WIN /* enable goodies for regular Windows platforms */ #endif #if defined(LUA_NUMBER_DOUBLE) && !defined(LUA_ANSI) /* { */ /* Microsoft compiler on a Pentium (32 bit) ? */ #if defined(LUA_WIN) && defined(_MSC_VER) && defined(_M_IX86) /* { */ #define LUA_MSASMTRICK #define LUA_IEEEENDIAN 0 #define LUA_NANTRICK /* pentium 32 bits? */ #elif defined(__i386__) || defined(__i386) || defined(__X86__) /* }{ */ #define LUA_IEEE754TRICK #define LUA_IEEELL #define LUA_IEEEENDIAN 0 #define LUA_NANTRICK /* pentium 64 bits? */ #elif defined(__x86_64) /* }{ */ #define LUA_IEEE754TRICK #define LUA_IEEEENDIAN 0 #elif defined(__POWERPC__) || defined(__ppc__) /* }{ */ #define LUA_IEEE754TRICK #define LUA_IEEEENDIAN 1 #else /* }{ */ /* assume IEEE754 and a 32-bit integer type */ #define LUA_IEEE754TRICK #endif /* } */ #endif /* } */ /********************************************************************/ /* extract of 5.2's llimits.h */ /* gives us lua_number2unsigned and lua_unsigned2number */ /* see copyright notice at the end of this file */ /********************************************************************/ #if defined(MS_ASMTRICK) || defined(LUA_MSASMTRICK) /* { */ /* trick with Microsoft assembler for X86 */ #define lua_number2unsigned(i,n) \ {__int64 l; __asm {__asm fld n __asm fistp l} i = (unsigned int)l;} #elif defined(LUA_IEEE754TRICK) /* }{ */ /* the next trick should work on any machine using IEEE754 with a 32-bit int type */ union compat52_luai_Cast { double l_d; LUA_INT32 l_p[2]; }; #if !defined(LUA_IEEEENDIAN) /* { */ #define LUAI_EXTRAIEEE \ static const union compat52_luai_Cast ieeeendian = {-(33.0 + 6755399441055744.0)}; #define LUA_IEEEENDIANLOC (ieeeendian.l_p[1] == 33) #else #define LUA_IEEEENDIANLOC LUA_IEEEENDIAN #define LUAI_EXTRAIEEE /* empty */ #endif /* } */ #define lua_number2int32(i,n,t) \ { LUAI_EXTRAIEEE \ volatile union compat52_luai_Cast u; u.l_d = (n) + 6755399441055744.0; \ (i) = (t)u.l_p[LUA_IEEEENDIANLOC]; } #define lua_number2unsigned(i,n) lua_number2int32(i, n, lua_Unsigned) #endif /* } */ /* the following definitions always work, but may be slow */ #if !defined(lua_number2unsigned) /* { */ /* the following definition assures proper modulo behavior */ #if defined(LUA_NUMBER_DOUBLE) || defined(LUA_NUMBER_FLOAT) #include #define SUPUNSIGNED ((lua_Number)(~(lua_Unsigned)0) + 1) #define lua_number2unsigned(i,n) \ ((i)=(lua_Unsigned)((n) - floor((n)/SUPUNSIGNED)*SUPUNSIGNED)) #else #define lua_number2unsigned(i,n) ((i)=(lua_Unsigned)(n)) #endif #endif /* } */ #if !defined(lua_unsigned2number) /* on several machines, coercion from unsigned to double is slow, so it may be worth to avoid */ #define lua_unsigned2number(u) \ (((u) <= (lua_Unsigned)INT_MAX) ? (lua_Number)(int)(u) : (lua_Number)(u)) #endif /********************************************************************/ static void compat52_call_lua (lua_State *L, char const code[], size_t len, int nargs, int nret) { lua_rawgetp(L, LUA_REGISTRYINDEX, (void*)code); if (lua_type(L, -1) != LUA_TFUNCTION) { lua_pop(L, 1); if (luaL_loadbuffer(L, code, len, "=none")) lua_error(L); lua_pushvalue(L, -1); lua_rawsetp(L, LUA_REGISTRYINDEX, (void*)code); } lua_insert(L, -nargs-1); lua_call(L, nargs, nret); } static const char compat52_arith_code[] = { 'l', 'o', 'c', 'a', 'l', ' ', 'o', 'p', ',', 'a', ',', 'b', '=', '.', '.', '.', '\n', 'i', 'f', ' ', 'o', 'p', '=', '=', '0', ' ', 't', 'h', 'e', 'n', '\n', 'r', 'e', 't', 'u', 'r', 'n', ' ', 'a', '+', 'b', '\n', 'e', 'l', 's', 'e', 'i', 'f', ' ', 'o', 'p', '=', '=', '1', ' ', 't', 'h', 'e', 'n', '\n', 'r', 'e', 't', 'u', 'r', 'n', ' ', 'a', '-', 'b', '\n', 'e', 'l', 's', 'e', 'i', 'f', ' ', 'o', 'p', '=', '=', '2', ' ', 't', 'h', 'e', 'n', '\n', 'r', 'e', 't', 'u', 'r', 'n', ' ', 'a', '*', 'b', '\n', 'e', 'l', 's', 'e', 'i', 'f', ' ', 'o', 'p', '=', '=', '3', ' ', 't', 'h', 'e', 'n', '\n', 'r', 'e', 't', 'u', 'r', 'n', ' ', 'a', '/', 'b', '\n', 'e', 'l', 's', 'e', 'i', 'f', ' ', 'o', 'p', '=', '=', '4', ' ', 't', 'h', 'e', 'n', '\n', 'r', 'e', 't', 'u', 'r', 'n', ' ', 'a', '%', 'b', '\n', 'e', 'l', 's', 'e', 'i', 'f', ' ', 'o', 'p', '=', '=', '5', ' ', 't', 'h', 'e', 'n', '\n', 'r', 'e', 't', 'u', 'r', 'n', ' ', 'a', '^', 'b', '\n', 'e', 'l', 's', 'e', 'i', 'f', ' ', 'o', 'p', '=', '=', '6', ' ', 't', 'h', 'e', 'n', '\n', 'r', 'e', 't', 'u', 'r', 'n', ' ', '-', 'a', '\n', 'e', 'n', 'd', '\n', '\0' }; void lua_arith (lua_State *L, int op) { if (op < LUA_OPADD && op > LUA_OPUNM) luaL_error(L, "invalid 'op' argument for lua_arith"); luaL_checkstack(L, 5, "not enough stack slots"); if (op == LUA_OPUNM) lua_pushvalue(L, -1); lua_pushnumber(L, op); lua_insert(L, -3); compat52_call_lua(L, compat52_arith_code, sizeof(compat52_arith_code)-1, 3, 1); } static const char compat52_compare_code[] = { 'l', 'o', 'c', 'a', 'l', ' ', 'a', ',', 'b', '=', '.', '.', '.', '\n', 'r', 'e', 't', 'u', 'r', 'n', ' ', 'a', '<', '=', 'b', '\n', '\0' }; int lua_compare (lua_State *L, int idx1, int idx2, int op) { int result = 0; switch (op) { case LUA_OPEQ: return lua_equal(L, idx1, idx2); case LUA_OPLT: return lua_lessthan(L, idx1, idx2); case LUA_OPLE: luaL_checkstack(L, 5, "not enough stack slots"); idx1 = lua_absindex(L, idx1); idx2 = lua_absindex(L, idx2); lua_pushvalue(L, idx1); lua_pushvalue(L, idx2); compat52_call_lua(L, (void*)compat52_compare_code, sizeof(compat52_compare_code)-1, 2, 1); result = lua_toboolean(L, -1); lua_pop(L, 1); return result; default: luaL_error(L, "invalid 'op' argument for lua_compare"); } return 0; } void lua_pushunsigned (lua_State *L, lua_Unsigned n) { lua_pushnumber(L, lua_unsigned2number(n)); } lua_Unsigned luaL_checkunsigned (lua_State *L, int i) { lua_Unsigned result; lua_Number n = lua_tonumber(L, i); if (n == 0 && !lua_isnumber(L, i)) luaL_checktype(L, i, LUA_TNUMBER); lua_number2unsigned(result, n); return result; } lua_Unsigned lua_tounsignedx (lua_State *L, int i, int *isnum) { lua_Unsigned result; lua_Number n = lua_tonumberx(L, i, isnum); lua_number2unsigned(result, n); return result; } lua_Unsigned luaL_optunsigned (lua_State *L, int i, lua_Unsigned def) { return luaL_opt(L, luaL_checkunsigned, i, def); } lua_Integer lua_tointegerx (lua_State *L, int i, int *isnum) { lua_Integer n = lua_tointeger(L, i); if (isnum != NULL) { *isnum = (n != 0 || lua_isnumber(L, i)); } return n; } void lua_len (lua_State *L, int i) { switch (lua_type(L, i)) { case LUA_TSTRING: /* fall through */ case LUA_TTABLE: if (!luaL_callmeta(L, i, "__len")) lua_pushnumber(L, (int)lua_objlen(L, i)); break; case LUA_TUSERDATA: if (luaL_callmeta(L, i, "__len")) break; /* maybe fall through */ default: luaL_error(L, "attempt to get length of a %s value", lua_typename(L, lua_type(L, i))); } } int luaL_len (lua_State *L, int i) { int res = 0, isnum = 0; luaL_checkstack(L, 1, "not enough stack slots"); lua_len(L, i); res = (int)lua_tointegerx(L, -1, &isnum); lua_pop(L, 1); if (!isnum) luaL_error(L, "object length is not a number"); return res; } const char *luaL_tolstring (lua_State *L, int idx, size_t *len) { if (!luaL_callmeta(L, idx, "__tostring")) { int t = lua_type(L, idx); switch (t) { case LUA_TNIL: lua_pushliteral(L, "nil"); break; case LUA_TSTRING: case LUA_TNUMBER: lua_pushvalue(L, idx); break; case LUA_TBOOLEAN: if (lua_toboolean(L, idx)) lua_pushliteral(L, "true"); else lua_pushliteral(L, "false"); break; default: lua_pushfstring(L, "%s: %p", lua_typename(L, t), lua_topointer(L, idx)); break; } } return lua_tolstring(L, -1, len); } void luaL_requiref (lua_State *L, char const* modname, lua_CFunction openf, int glb) { luaL_checkstack(L, 3, "not enough stack slots"); lua_pushcfunction(L, openf); lua_pushstring(L, modname); lua_call(L, 1, 1); lua_getglobal(L, "package"); lua_getfield(L, -1, "loaded"); lua_replace(L, -2); lua_pushvalue(L, -2); lua_setfield(L, -2, modname); lua_pop(L, 1); if (glb) { lua_pushvalue(L, -1); lua_setglobal(L, modname); } } void luaL_buffinit (lua_State *L, luaL_Buffer_52 *B) { /* make it crash if used via pointer to a 5.1-style luaL_Buffer */ B->b.p = NULL; B->b.L = NULL; B->b.lvl = 0; /* reuse the buffer from the 5.1-style luaL_Buffer though! */ B->ptr = B->b.buffer; B->capacity = LUAL_BUFFERSIZE; B->nelems = 0; B->L2 = L; } char *luaL_prepbuffsize (luaL_Buffer_52 *B, size_t s) { if (B->capacity - B->nelems < s) { /* needs to grow */ char* newptr = NULL; size_t newcap = B->capacity * 2; if (newcap - B->nelems < s) newcap = B->nelems + s; if (newcap < B->capacity) /* overflow */ luaL_error(B->L2, "buffer too large"); newptr = lua_newuserdata(B->L2, newcap); memcpy(newptr, B->ptr, B->nelems); if (B->ptr != B->b.buffer) lua_replace(B->L2, -2); /* remove old buffer */ B->ptr = newptr; B->capacity = newcap; } return B->ptr+B->nelems; } void luaL_addlstring (luaL_Buffer_52 *B, const char *s, size_t l) { memcpy(luaL_prepbuffsize(B, l), s, l); luaL_addsize(B, l); } void luaL_addvalue (luaL_Buffer_52 *B) { size_t len = 0; const char *s = lua_tolstring(B->L2, -1, &len); if (!s) luaL_error(B->L2, "cannot convert value to string"); if (B->ptr != B->b.buffer) lua_insert(B->L2, -2); /* userdata buffer must be at stack top */ luaL_addlstring(B, s, len); lua_remove(B->L2, B->ptr != B->b.buffer ? -2 : -1); } void luaL_pushresult (luaL_Buffer_52 *B) { lua_pushlstring(B->L2, B->ptr, B->nelems); if (B->ptr != B->b.buffer) lua_replace(B->L2, -2); /* remove userdata buffer */ } #endif /* LUA_VERSION_NUM == 501 */ /********************************************************************* * This file contains parts of Lua 5.2's source code: * * Copyright (C) 1994-2013 Lua.org, PUC-Rio. * * 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. *********************************************************************/ ================================================ FILE: src/lcurses/compat-5.2.h ================================================ #include #include #include #include "../lua.h" #include "../lauxlib.h" #include "../lualib.h" #if !defined(LUA_VERSION_NUM) /* Lua 5.0 */ #define LUA_QL(x) "'" x "'" #define LUA_QS LUA_QL("%s") #define luaL_Reg luaL_reg #define luaL_opt(L, f, n, d) \ (lua_isnoneornil(L, n) ? (d) : f(L, n)) #define luaL_addchar(B,c) \ ((void)((B)->p < ((B)->buffer+LUAL_BUFFERSIZE) || luaL_prepbuffer(B)), \ (*(B)->p++ = (char)(c))) #endif /* Lua 5.0 */ #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM == 501 /* Lua 5.1 */ /* PUC-Rio Lua uses lconfig_h as include guard for luaconf.h, * LuaJIT uses luaconf_h. If you use PUC-Rio's include files * but LuaJIT's library, you will need to define the macro * COMPAT52_IS_LUAJIT yourself! */ #if !defined(COMPAT52_IS_LUAJIT) && defined(luaconf_h) #define COMPAT52_IS_LUAJIT #endif /* LuaJIT doesn't define these unofficial macros ... */ #if !defined(LUAI_INT32) #include #if INT_MAX-20 < 32760 #define LUAI_INT32 long #define LUAI_UINT32 unsigned long #elif INT_MAX > 2147483640L #define LUAI_INT32 int #define LUAI_UINT32 unsigned int #else #error "could not detect suitable lua_Unsigned datatype" #endif #endif #define LUA_OPADD 0 #define LUA_OPSUB 1 #define LUA_OPMUL 2 #define LUA_OPDIV 3 #define LUA_OPMOD 4 #define LUA_OPPOW 5 #define LUA_OPUNM 6 #define LUA_OPEQ 0 #define LUA_OPLT 1 #define LUA_OPLE 2 typedef LUAI_UINT32 lua_Unsigned; typedef struct luaL_Buffer_52 { luaL_Buffer b; /* make incorrect code crash! */ char *ptr; size_t nelems; size_t capacity; lua_State *L2; } luaL_Buffer_52; #define luaL_Buffer luaL_Buffer_52 typedef struct luaL_Stream { FILE *f; /* The following field is for LuaJIT which adds a uint32_t field * to file handles. */ lua_Unsigned type; lua_CFunction closef; } luaL_Stream; #define lua_tounsigned(L, i) lua_tounsignedx(L, i, NULL) #define lua_rawlen(L, i) lua_objlen(L, i) void lua_arith (lua_State *L, int op); int lua_compare (lua_State *L, int idx1, int idx2, int op); void lua_pushunsigned (lua_State *L, lua_Unsigned n); lua_Unsigned luaL_checkunsigned (lua_State *L, int i); lua_Unsigned lua_tounsignedx (lua_State *L, int i, int *isnum); lua_Unsigned luaL_optunsigned (lua_State *L, int i, lua_Unsigned def); lua_Integer lua_tointegerx (lua_State *L, int i, int *isnum); void lua_len (lua_State *L, int i); int luaL_len (lua_State *L, int i); const char *luaL_tolstring (lua_State *L, int idx, size_t *len); void luaL_requiref (lua_State *L, char const* modname, lua_CFunction openf, int glb); #define luaL_buffinit luaL_buffinit_52 void luaL_buffinit (lua_State *L, luaL_Buffer_52 *B); #define luaL_prepbuffsize luaL_prepbuffsize_52 char *luaL_prepbuffsize (luaL_Buffer_52 *B, size_t s); #define luaL_addlstring luaL_addlstring_52 void luaL_addlstring (luaL_Buffer_52 *B, const char *s, size_t l); #define luaL_addvalue luaL_addvalue_52 void luaL_addvalue (luaL_Buffer_52 *B); #define luaL_pushresult luaL_pushresult_52 void luaL_pushresult (luaL_Buffer_52 *B); #undef luaL_buffinitsize #define luaL_buffinitsize(L, B, s) \ (luaL_buffinit(L, B), luaL_prepbuffsize(B, s)) #undef luaL_prepbuffer #define luaL_prepbuffer(B) \ luaL_prepbuffsize(B, LUAL_BUFFERSIZE) #undef luaL_addchar #define luaL_addchar(B, c) \ ((void)((B)->nelems < (B)->capacity || luaL_prepbuffsize(B, 1)), \ ((B)->ptr[(B)->nelems++] = (c))) #undef luaL_addsize #define luaL_addsize(B, s) \ ((B)->nelems += (s)) #undef luaL_addstring #define luaL_addstring(B, s) \ luaL_addlstring(B, s, strlen(s)) #undef luaL_pushresultsize #define luaL_pushresultsize(B, s) \ (luaL_addsize(B, s), luaL_pushresult(B)) #endif /* Lua 5.1 */ #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM == 501 /* Lua 5.0 *or* 5.1 */ #define LUA_OK 0 #define lua_pushglobaltable(L) \ lua_pushvalue(L, LUA_GLOBALSINDEX) #define luaL_newlib(L, l) \ (lua_newtable((L)),luaL_setfuncs((L), (l), 0)) void luaL_checkversion (lua_State *L); #endif /* Lua 5.0 *or* 5.1 */ int lua_absindex (lua_State *L, int i); void lua_copy (lua_State *L, int from, int to); void lua_rawgetp (lua_State *L, int i, const void *p); void lua_rawsetp (lua_State *L, int i, const void *p); void *luaL_testudata (lua_State *L, int i, const char *tname); lua_Number lua_tonumberx (lua_State *L, int i, int *isnum); void lua_getuservalue (lua_State *L, int i); void lua_setuservalue (lua_State *L, int i); void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup); void luaL_setmetatable (lua_State *L, const char *tname); int luaL_getsubtable (lua_State *L, int i, const char *name); void luaL_traceback (lua_State *L, lua_State *L1, const char *msg, int level); int luaL_fileresult (lua_State *L, int stat, const char *fname); ================================================ FILE: src/lcurses/curses.c ================================================ /* * Curses binding for Lua 5.1, 5.2 & 5.3. * * (c) Gary V. Vaughan 2013-2017 * (c) Reuben Thomas 2009-2012 * (c) Tiago Dionizio 2004-2007 * * 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. */ /*** Full-screen Text Terminal Manipulation. Some of the C functions beginning with "no" do not exist in Lua. You should use `curses.nl(false)` and `curses.nl(true)` instead of `nonl()` and `nl()`, and likewise `curses.echo(false)` and `curses.echo(true)` instead of `noecho()` and `echo()` . In this Lua module the `stdscr:getch()` function always returns an integer. In C, a single character is an integer, but in Lua (and Perl) a single character is a short string. The Perl Curses function `getch()` returns a char if it was a char, and a number if it was a constant; to get this behaviour in Lua you have to convert explicitly, e.g.: if c < 256 then c = string.char(c) end Some Lua functions take a different set of parameters than their C counterparts; for example, you should use `y, x = stdscr.getyx()` instead of `getstr(str)` and `getyx(y, x)`, and likewise for `getbegyx` and `getmaxyx` and `getparyx` and `pair_content`. The Perl Curses module now uses the C-compatible parameters, so be aware of this difference when translating code from Perl into Lua, as well as from C into Lua. Many curses functions have variants starting with the prefixes `w-`, `mv-`, and/or `wmv-`. These variants differ only in the explicit addition of a window, or by the addition of two coordinates that are used to move the cursor first. For example, in C `addch()` has three other variants: `waddch()`, `mvaddch()` and `mvwaddch()`. The Lua equivalents, respectively being `stdscr:addch()`, `somewindow:addch()`, `stdscr:mvaddch()` and `somewindow:mvaddch()`, with the window argument passed implicitly with Lua's `:` syntax sugar. @module curses */ #include "_helpers.c" #ifdef __linux__ #include "strlcpy.c" #endif #include "chstr.c" #include "window.c" static const char *STDSCR_REGISTRY = "curses:stdscr"; static const char *RIPOFF_TABLE = "curses:ripoffline"; /*** Create a new line drawing buffer instance. @function new_chstr @int len number of element to allocate @treturn chstr a new char buffer object @see curses.chstr */ static int Pnew_chstr(lua_State *L) { int len = checkint(L, 1); chstr* ncs = chstr_new(L, len); /* defined in curses/chstr.c */ memset(ncs->str, ' ', len*sizeof(chtype)); return 1; } #define CCR(n, v) \ lua_pushstring(L, n); \ lua_pushinteger(L, v); \ lua_settable(L, -3); #define CC(s) CCR(#s, s) #define CF(i) CCR(LCURSES_STR(LCURSES_SPLICE(KEY_F, i)), KEY_F(i)) /* ** some of these values are not constant so need to register ** them directly instead of using a table */ static void register_curses_constants(lua_State *L) { /* colors */ CC(COLOR_BLACK); CC(COLOR_RED); CC(COLOR_GREEN); CC(COLOR_YELLOW); CC(COLOR_BLUE); CC(COLOR_MAGENTA); CC(COLOR_CYAN); CC(COLOR_WHITE); /* alternate character set */ CC(ACS_BLOCK); CC(ACS_BOARD); CC(ACS_BTEE); CC(ACS_TTEE); CC(ACS_LTEE); CC(ACS_RTEE); CC(ACS_LLCORNER); CC(ACS_LRCORNER); CC(ACS_URCORNER); CC(ACS_ULCORNER); CC(ACS_LARROW); CC(ACS_RARROW); CC(ACS_UARROW); CC(ACS_DARROW); CC(ACS_HLINE); CC(ACS_VLINE); CC(ACS_BULLET); CC(ACS_CKBOARD); CC(ACS_LANTERN); CC(ACS_DEGREE); CC(ACS_DIAMOND); CC(ACS_PLMINUS); CC(ACS_PLUS); CC(ACS_S1); CC(ACS_S9); /* attributes */ CC(A_NORMAL); CC(A_STANDOUT); CC(A_UNDERLINE); CC(A_REVERSE); CC(A_BLINK); CC(A_DIM); CC(A_BOLD); CC(A_PROTECT); CC(A_INVIS); CC(A_ALTCHARSET); CC(A_CHARTEXT); CC(A_ATTRIBUTES); #ifdef A_COLOR CC(A_COLOR); #endif /* key functions */ CC(KEY_BREAK); CC(KEY_DOWN); CC(KEY_UP); CC(KEY_LEFT); CC(KEY_RIGHT); CC(KEY_HOME); CC(KEY_BACKSPACE); CC(KEY_DL); CC(KEY_IL); CC(KEY_DC); CC(KEY_IC); CC(KEY_EIC); CC(KEY_CLEAR); CC(KEY_EOS); CC(KEY_EOL); CC(KEY_SF); CC(KEY_SR); CC(KEY_NPAGE); CC(KEY_PPAGE); CC(KEY_STAB); CC(KEY_CTAB); CC(KEY_CATAB); CC(KEY_ENTER); CC(KEY_SRESET); CC(KEY_RESET); CC(KEY_PRINT); CC(KEY_LL); CC(KEY_A1); CC(KEY_A3); CC(KEY_B2); CC(KEY_C1); CC(KEY_C3); CC(KEY_BTAB); CC(KEY_BEG); CC(KEY_CANCEL); CC(KEY_CLOSE); CC(KEY_COMMAND); CC(KEY_COPY); CC(KEY_CREATE); CC(KEY_END); CC(KEY_EXIT); CC(KEY_FIND); CC(KEY_HELP); CC(KEY_MARK); CC(KEY_MESSAGE); /* ncurses extension: CC(KEY_MOUSE); */ CC(KEY_MOVE); CC(KEY_NEXT); CC(KEY_OPEN); CC(KEY_OPTIONS); CC(KEY_PREVIOUS); CC(KEY_REDO); CC(KEY_REFERENCE); CC(KEY_REFRESH); CC(KEY_REPLACE); CC(KEY_RESIZE); CC(KEY_RESTART); CC(KEY_RESUME); CC(KEY_SAVE); CC(KEY_SBEG); CC(KEY_SCANCEL); CC(KEY_SCOMMAND); CC(KEY_SCOPY); CC(KEY_SCREATE); CC(KEY_SDC); CC(KEY_SDL); CC(KEY_SELECT); CC(KEY_SEND); CC(KEY_SEOL); CC(KEY_SEXIT); CC(KEY_SFIND); CC(KEY_SHELP); CC(KEY_SHOME); CC(KEY_SIC); CC(KEY_SLEFT); CC(KEY_SMESSAGE); CC(KEY_SMOVE); CC(KEY_SNEXT); CC(KEY_SOPTIONS); CC(KEY_SPREVIOUS); CC(KEY_SPRINT); CC(KEY_SREDO); CC(KEY_SREPLACE); CC(KEY_SRIGHT); CC(KEY_SRSUME); CC(KEY_SSAVE); CC(KEY_SSUSPEND); CC(KEY_SUNDO); CC(KEY_SUSPEND); CC(KEY_UNDO); /* KEY_Fx 0 <= x <= 63 */ CC(KEY_F0); CF(1); CF(2); CF(3); CF(4); CF(5); CF(6); CF(7); CF(8); CF(9); CF(10); CF(11); CF(12); CF(13); CF(14); CF(15); CF(16); CF(17); CF(18); CF(19); CF(20); CF(21); CF(22); CF(23); CF(24); CF(25); CF(26); CF(27); CF(28); CF(29); CF(30); CF(31); CF(32); CF(33); CF(34); CF(35); CF(36); CF(37); CF(38); CF(39); CF(40); CF(41); CF(42); CF(43); CF(44); CF(45); CF(46); CF(47); CF(48); CF(49); CF(50); CF(51); CF(52); CF(53); CF(54); CF(55); CF(56); CF(57); CF(58); CF(59); CF(60); CF(61); CF(62); CF(63); } /* ** make sure screen is restored (and cleared) at exit ** (for the situations where program is aborted without a ** proper cleanup) */ void cleanup_curses(void) { if (!isendwin()) { wclear(stdscr); wrefresh(stdscr); endwin(); } } extern void stack_dump(lua_State *L); static int init_stdscr(lua_State *L) { /* return stdscr - main window */ lc_newwin(L, stdscr); /* save main window on registry */ lua_pushstring(L, STDSCR_REGISTRY); lua_pushvalue(L, -2); lua_rawset(L, LUA_REGISTRYINDEX); /* setup curses constants - curses.xxx numbers */ lua_pushvalue(L, -2); register_curses_constants(L); /* install cleanup handler to help in debugging and screen trashing */ atexit(cleanup_curses); return 1; } /*** Clean up terminal prior to exiting or escaping curses. @function endwin @treturn bool `true`, if successful @see endwin(3x) */ static int Pendwin(lua_State *L) { return pushokresult(endwin()); } /*** Has @{endwin} been called more recently than @{curses.window:refresh}? @function isendwin @treturn bool whether @{endwin} has been called @see isendwin(3x) */ static int Pisendwin(lua_State *L) { return pushboolresult(isendwin()); } /*** Retern the main screen window. @function stdscr @treturn window main screen @see initscr @see stdscr(3x) */ static int Pstdscr(lua_State *L) { lua_pushstring(L, STDSCR_REGISTRY); lua_rawget(L, LUA_REGISTRYINDEX); return 1; } /*** Number of columns in the main screen window. @function cols @treturn int number of columns in the main screen @see lines @see stdscr @see COLS(3x) */ static int Pcols(lua_State *L) { return pushintresult(COLS); } /*** Number of lines in the main screen window. @function lines @treturn int number of lines in the main screen @see cols @see stdscr @see LINES(3x) */ static int Plines(lua_State *L) { return pushintresult(LINES); } /*** Initialise color output facility. @function start_color @treturn bool `true`, if successful @see can_change_color(3x) @see has_colors */ static int Pstart_color(lua_State *L) { return pushokresult(start_color()); } /*** Does the terminal have color capability? @function has_colors @treturn bool `true`, if the terminal supports colors @see can_change_color(3x) @see start_color @usage if curses.has_colors () then curses.start_color () end */ static int Phas_colors(lua_State *L) { return pushboolresult(has_colors()); } /*** Reserve `-1` to represent terminal default colors. @function use_default_colors @treturn bool `true`, if successful @see use_default_colors(3x) @fixme ncurses only? */ static int Puse_default_colors(lua_State *L) { return pushokresult(use_default_colors()); } /*** Set -1 foreground and background colors. @function use_default_colors @treturn bool `true`, if successful @see use_default_colors(3x) @fixme ncurses only? */ static int Passume_default_colors(lua_State *L) { int fg = checkint(L, 1); int bg = checkint(L, 2); return pushokresult(assume_default_colors(fg, bg)); } /*** Associate a color pair id with a specific foreground and background color. @function init_pair @int pair color pair id to act on @int f foreground color to assign @int b background color to assign @treturn bool `true`, if successful @see init_pair(3x) @see pair_content */ static int Pinit_pair(lua_State *L) { short pair = checkint(L, 1); short f = checkint(L, 2); short b = checkint(L, 3); return pushokresult(init_pair(pair, f, b)); } /*** Return the foreground and background colors associated with a color pair id. @function pair_content @int pair color pair id to act on @treturn int foreground color of *pair* @treturn int background color of *pair* @see can_change_color(3x) @see init_pair */ static int Ppair_content(lua_State *L) { short pair = checkint(L, 1); short f; short b; int ret = pair_content(pair, &f, &b); if (ret == ERR) return 0; lua_pushinteger(L, f); lua_pushinteger(L, b); return 2; } /*** How many colors are available for this terminal? @function colors @treturn int total number of available colors @see can_change_color(3x) @see color_pairs */ static int Pcolors(lua_State *L) { return pushintresult(COLORS); } /*** How may distinct color pairs are supported by this terminal? @function color_pairs @treturn int total number of available color pairs @see can_change_color(3x) @see colors */ static int Pcolor_pairs(lua_State *L) { return pushintresult(COLOR_PAIRS); } /*** Return the attributes for the given color pair id. @function color_pair @int pair color pair id to act on @treturn int attributes for color pair *pair* @see can_change_color(3x) */ static int Pcolor_pair(lua_State *L) { int n = checkint(L, 1); return pushintresult(COLOR_PAIR(n)); } /*** Fetch the output speed of the terminal. @function baudrate @treturn int output speed of the terminal in bits-per-second @see baudrate(3x) */ static int Pbaudrate(lua_State *L) { return pushintresult(baudrate()); } /*** Fetch the terminal's current erase character. @function erasechar @treturn int current erase character @see erasechar(3x) */ static int Perasechar(lua_State *L) { return pushintresult(erasechar()); } /*** Fetch the character insert and delete capability of the terminal. @function has_ic @treturn bool `true`, if the terminal has insert and delete character operations @see has_ic(3x) */ static int Phas_ic(lua_State *L) { return pushboolresult(has_ic()); } /*** Fetch the line insert and delete capability of the terminal. @function has_il @treturn bool `true`, if the terminal has insert and delete line operations @see has_il(3x) */ static int Phas_il(lua_State *L) { return pushboolresult(has_il()); } /*** Fetch the terminal's current kill character. @function killchar @treturn int current line kill character @see killchar(3x) */ static int Pkillchar(lua_State *L) { return pushintresult(killchar()); } /*** Bitwise OR of all (or selected) video attributes supported by the terminal. @function termattrs @int[opt] a terminal attribute bits @treturn[1] bool `true`, if the terminal supports attribute *a* @treturn[2] int bitarray of supported terminal attributes @see termattrs(3x) */ static int Ptermattrs(lua_State *L) { if (lua_gettop(L) > 0) { int a = checkint(L, 1); return pushboolresult(termattrs() & a); } return pushintresult(termattrs()); } /*** Fetch the name of the terminal. @function termname @treturn string terminal name @see termname(3x) */ static int Ptermname(lua_State *L) { return pushstringresult(termname()); } /*** Fetch the verbose name of the terminal. @function longname @treturn string verbose description of the current terminal @see longname(3x) */ static int Plongname(lua_State *L) { return pushstringresult(longname()); } /* there is no easy way to implement this... */ static lua_State *rip_L = NULL; static int ripoffline_cb(WINDOW* w, int cols) { static int line = 0; int top = lua_gettop(rip_L); /* better be safe */ if (!lua_checkstack(rip_L, 5)) return 0; /* get the table from the registry */ lua_pushstring(rip_L, RIPOFF_TABLE); lua_gettable(rip_L, LUA_REGISTRYINDEX); /* get user callback function */ if (lua_isnil(rip_L, -1)) { lua_pop(rip_L, 1); return 0; } lua_rawgeti(rip_L, -1, ++line); /* function to be called */ lc_newwin(rip_L, w); /* create window object */ lua_pushinteger(rip_L, cols); /* push number of columns */ lua_pcall(rip_L, 2, 0, 0); /* call the lua function */ lua_settop(rip_L, top); return 1; } /*** Reduce the available size of the main screen. @function ripoffline @bool top_line @func callback @treturn bool `true`, if successful @see ripoffline(3x) */ static int Pripoffline(lua_State *L) { static int rip = 0; int top_line = lua_toboolean(L, 1); if (!lua_isfunction(L, 2)) { lua_pushliteral(L, "invalid callback passed as second parameter"); lua_error(L); } /* need to save the lua state somewhere... */ rip_L = L; /* get the table where we are going to save the callbacks */ lua_pushstring(L, RIPOFF_TABLE); lua_gettable(L, LUA_REGISTRYINDEX); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_newtable(L); lua_pushstring(L, RIPOFF_TABLE); lua_pushvalue(L, -2); lua_settable(L, LUA_REGISTRYINDEX); } /* save function callback in registry table */ lua_pushvalue(L, 2); lua_rawseti(L, -2, ++rip); /* and tell curses we are going to take the line */ return pushokresult(ripoffline(top_line ? 1 : -1, ripoffline_cb)); } /*** Change the visibility of the cursor. @function curs_set @int vis one of `0` (invisible), `1` (visible) or `2` (very visible) @treturn[1] int previous cursor state @return[2] nil if *vis* is not supported @see curs_set(3x) */ static int Pcurs_set(lua_State *L) { int vis = checkint(L, 1); int state = curs_set(vis); if (state == ERR) return 0; return pushintresult(state); } /*** Sleep for a few milliseconds. @function napms @int ms time to wait in milliseconds @treturn bool `true`, if successful @see napms(3x) @see delay_output */ static int Pnapms(lua_State *L) { int ms = checkint(L, 1); return pushokresult(napms(ms)); } /*** Change the terminal size. @function resizeterm @int nlines number of lines @int ncols number of columns @treturn bool `true`, if successful @raise unimplemented @fixme ncurses only? */ static int Presizeterm(lua_State *L) { int nlines = checkint(L, 1); int ncols = checkint(L, 2); return pushokresult(resizeterm (nlines, ncols)); } /*** Send the terminal audible bell. @function beep @treturn bool `true`, if successful @see beep(3x) @see flash */ static int Pbeep(lua_State *L) { return pushokresult(beep()); } /*** Send the terminal visible bell. @function flash @treturn bool `true`, if successful @see flash(3x) @see beep */ static int Pflash(lua_State *L) { return pushokresult(flash()); } /*** Create a new window. @function newwin @int nlines number of window lines @int ncols number of window columns @int begin_y top line of window @int begin_x leftmost column of window @treturn window a new window object @see newwin(3x) @see curses.window */ static int Pnewwin(lua_State *L) { int nlines = checkint(L, 1); int ncols = checkint(L, 2); int begin_y = checkint(L, 3); int begin_x = checkint(L, 4); lc_newwin(L, newwin(nlines, ncols, begin_y, begin_x)); return 1; } /*** Refresh the visible terminal screen. @function doupdate @treturn bool `true`, if successful @see doupdate(3x) @see curses.window:refresh */ static int Pdoupdate(lua_State *L) { return pushokresult(doupdate()); } /*** Initialise the soft label keys area. This must be called before @{initscr}. @function slk_init @int fmt @treturn bool `true`, if successful @see slk_init(3x) */ static int Pslk_init(lua_State *L) { int fmt = checkint(L, 1); return pushokresult(slk_init(fmt)); } /*** Set the label for a soft label key. @function slk_set @int labnum @string label @int fmt @treturn bool `true`, if successful @see slk_set(3x) */ static int Pslk_set(lua_State *L) { int labnum = checkint(L, 1); const char* label = luaL_checkstring(L, 2); int fmt = checkint(L, 3); return pushokresult(slk_set(labnum, label, fmt)); } /*** Refresh the soft label key area. @function slk_refresh @treturn bool `true`, if successful @see slk_refresh(3x) @see curses.window:refresh */ static int Pslk_refresh(lua_State *L) { return pushokresult(slk_refresh()); } /*** Copy the soft label key area backing screen to the virtual screen. @function slk_noutrefresh @treturn bool `true`, if successful @see slk_noutrefresh(3x) @see curses.window:refresh */ static int Pslk_noutrefresh(lua_State *L) { return pushokresult(slk_noutrefresh()); } /*** Fetch the label for a soft label key. @function slk_label @int labnum @treturn string current label for *labnum* @see slk_label(3x) */ static int Pslk_label(lua_State *L) { int labnum = checkint(L, 1); return pushstringresult(slk_label(labnum)); } /*** Clears the soft labels from the screen. @function slk_clear @treturn bool `true`, if successful @see slk_clear(3x) @see slk_restore */ static int Pslk_clear(lua_State *L) { return pushokresult(slk_clear()); } /*** Restores the soft labels to the screen. @function slk_restore @treturn bool `true`, if successful @see slk_restore(3x) @see slk_clear */ static int Pslk_restore(lua_State *L) { return pushokresult(slk_restore()); } /*** Mark the soft label key area for refresh. @function slk_touch @treturn bool `true`, if successful @see slk_touch(3x) */ static int Pslk_touch(lua_State *L) { return pushokresult(slk_touch()); } /*** Enable an attribute for soft labels. @function slk_attron @int attrs @treturn bool `true`, if successful @see slk_attron(3x) */ static int Pslk_attron(lua_State *L) { chtype attrs = checkch(L, 1); return pushokresult(slk_attron(attrs)); } /*** Disable an attribute for soft labels. @function slk_attroff @int attrs @treturn bool `true`, if successful @see slk_attroff(3x) */ static int Pslk_attroff(lua_State *L) { chtype attrs = checkch(L, 1); return pushokresult(slk_attroff(attrs)); } /*** Set the attributes for soft labels. @function slk_attrset @int attrs @treturn bool `true`, if successful @see slk_attrset(3x) */ static int Pslk_attrset(lua_State *L) { chtype attrs = checkch(L, 1); return pushokresult(slk_attrset(attrs)); } /*** Put the terminal into cbreak mode. @function cbreak @bool[opt] on @treturn bool `true`, if successful @see cbreak(3x) @see nocbreak(3x) */ static int Pcbreak(lua_State *L) { if (lua_isnoneornil(L, 1) || lua_toboolean(L, 1)) return pushokresult(cbreak()); return pushokresult(nocbreak()); } /*** Whether characters are echoed to the terminal as they are typed. @function echo @bool[opt] on @treturn bool `true`, if successful @see echo(3x) @see noecho(3x) */ static int Pecho(lua_State *L) { if (lua_isnoneornil(L, 1) || lua_toboolean(L, 1)) return pushokresult(echo()); return pushokresult(noecho()); } /*** Put the terminal into raw mode. @function raw @bool[opt] on @treturn bool `true`, if successful @see noraw(3x) @see raw(3x) */ static int Praw(lua_State *L) { if (lua_isnoneornil(L, 1) || lua_toboolean(L, 1)) return pushokresult(raw()); return pushokresult(noraw()); } /*** Put the terminal into halfdelay mode. @function halfdelay @int tenths delay in tenths of a second @treturn bool `true`, if successful @see halfdelay(3x) */ static int Phalfdelay(lua_State *L) { int tenths = checkint(L, 1); return pushokresult(halfdelay(tenths)); } /*** Whether to translate a return key to newline on input. @function nl @bool[opt] on @treturn bool `true`, if successful @see nl(3x) @see nonl(3x) */ static int Pnl(lua_State *L) { if (lua_isnoneornil(L, 1) || lua_toboolean(L, 1)) return pushokresult(nl()); return pushokresult(nonl()); } /*** Return a printable representation of a character, ignoring attributes. @function unctrl @int c character to act on @treturn string printable representation of *c* @see unctrl(3x) @see keyname */ static int Punctrl(lua_State *L) { chtype c = checkch(L, 1); return pushstringresult(unctrl(c)); } /*** Return a printable representation of a key. @function keyname @int c a key @treturn string key name of *c* @see keyname(3x) @see unctrl */ static int Pkeyname(lua_State *L) { int c = checkint(L, 1); return pushstringresult(keyname(c)); } /*** Insert padding characters to force a short delay. @function delay_output @int ms delay time in milliseconds @treturn bool `true`, if successful @see napms @fixme ncurses only? */ static int Pdelay_output(lua_State *L) { int ms = checkint(L, 1); return pushokresult(delay_output(ms)); } /*** Throw away any typeahead in the keyboard input buffer. @function flushinp @treturn bool `true`, if successful @see flushinp(3x) @see ungetch */ static int Pflushinp(lua_State *L) { return pushboolresult(flushinp()); } /*** Return a character to the keyboard input buffer. @function ungetch @int c @treturn bool `true`, if successful @see ungetch(3x) @see flushinp */ static int Pungetch(lua_State *L) { int c = checkint(L, 1); return pushokresult(ungetch(c)); } /*** Create a new pad. @function newpad @int nlines @int ncols @treturn bool `true`, if successful @see newpad(3x) */ static int Pnewpad(lua_State *L) { int nlines = checkint(L, 1); int ncols = checkint(L, 2); lc_newwin(L, newpad(nlines, ncols)); return 1; } static char ti_capname[32]; /*** Fetch terminfo boolean capability. @function tigetflag @string capname @treturn bool content of terminal boolean capability @see tigetflag(3x) @see terminfo(5) */ static int Ptigetflag (lua_State *L) { int r; strlcpy (ti_capname, luaL_checkstring (L, 1), sizeof (ti_capname)); r = tigetflag (ti_capname); if (-1 == r) return luaL_error (L, "`%s' is not a boolean capability", ti_capname); return pushboolresult (r); } /*** Fetch terminfo numeric capability. @function tigetnum @string capname @treturn int content of terminal numeric capability @see tigetnum(3x) @see terminfo(5) */ static int Ptigetnum (lua_State *L) { int res; strlcpy (ti_capname, luaL_checkstring (L, 1), sizeof (ti_capname)); res = tigetnum (ti_capname); if (-2 == res) return luaL_error (L, "`%s' is not a numeric capability", ti_capname); else if (-1 == res) lua_pushnil (L); else lua_pushinteger(L, res); return 1; } /*** Fetch terminfo string capability. @function tigetstr @string capname @treturn string content of terminal string capability @see tigetstr(3x) @see terminfo(5) */ static int Ptigetstr (lua_State *L) { const char *res; strlcpy (ti_capname, luaL_checkstring (L, 1), sizeof (ti_capname)); res = tigetstr (ti_capname); if ((char *) -1 == res) return luaL_error (L, "`%s' is not a string capability", ti_capname); else if (NULL == res) lua_pushnil (L); else lua_pushstring(L, res); return 1; } static const luaL_Reg curseslib[] = { LCURSES_FUNC( Pbaudrate ), LCURSES_FUNC( Pbeep ), LCURSES_FUNC( Pcbreak ), LCURSES_FUNC( Pcolor_pair ), LCURSES_FUNC( Pcolor_pairs ), LCURSES_FUNC( Pcolors ), LCURSES_FUNC( Pcols ), LCURSES_FUNC( Pcurs_set ), LCURSES_FUNC( Pdelay_output ), LCURSES_FUNC( Pdoupdate ), LCURSES_FUNC( Pecho ), LCURSES_FUNC( Pendwin ), LCURSES_FUNC( Perasechar ), LCURSES_FUNC( Pflash ), LCURSES_FUNC( Pflushinp ), LCURSES_FUNC( Phalfdelay ), LCURSES_FUNC( Phas_colors ), LCURSES_FUNC( Phas_ic ), LCURSES_FUNC( Phas_il ), LCURSES_FUNC( Pinit_pair ), LCURSES_FUNC( Pisendwin ), LCURSES_FUNC( Pkeyname ), LCURSES_FUNC( Pkillchar ), LCURSES_FUNC( Plines ), LCURSES_FUNC( Plongname ), LCURSES_FUNC( Pnapms ), LCURSES_FUNC( Pnew_chstr ), LCURSES_FUNC( Pnewpad ), LCURSES_FUNC( Pnewwin ), LCURSES_FUNC( Pnl ), LCURSES_FUNC( Ppair_content ), LCURSES_FUNC( Praw ), LCURSES_FUNC( Presizeterm ), LCURSES_FUNC( Pripoffline ), LCURSES_FUNC( Pslk_attroff ), LCURSES_FUNC( Pslk_attron ), LCURSES_FUNC( Pslk_attrset ), LCURSES_FUNC( Pslk_clear ), LCURSES_FUNC( Pslk_init ), LCURSES_FUNC( Pslk_label ), LCURSES_FUNC( Pslk_noutrefresh ), LCURSES_FUNC( Pslk_refresh ), LCURSES_FUNC( Pslk_restore ), LCURSES_FUNC( Pslk_set ), LCURSES_FUNC( Pslk_touch ), LCURSES_FUNC( Pstart_color ), LCURSES_FUNC( Pstdscr ), LCURSES_FUNC( Ptermattrs ), LCURSES_FUNC( Ptermname ), LCURSES_FUNC( Ptigetflag ), LCURSES_FUNC( Ptigetnum ), LCURSES_FUNC( Ptigetstr ), LCURSES_FUNC( Punctrl ), LCURSES_FUNC( Pungetch ), LCURSES_FUNC( Puse_default_colors), LCURSES_FUNC( Passume_default_colors), {NULL, NULL} }; /*** Constants. @section constants */ /*** Curses constants. Any constants not available in the underlying system will be `nil` valued, see @{curses.lua}. Many of the `KEY_` constants cannot be generated by modern keyboards and are mostly for historical compatibility with ancient terminal hardware keyboards. Note that almost all of these constants remain undefined (`nil`) until after @{curses.initscr} has returned successfully. @table curses @int ACS_BLOCK alternate character set solid block @int ACS_BOARD alternate character set board of squares @int ACS_BTEE alternate character set bottom-tee @int ACS_BULLET alternate character set bullet @int ACS_CKBOARD alternate character set stipple @int ACS_DARROW alternate character set down arrow @int ACS_DEGREE alternate character set degrees mark @int ACS_DIAMOND alternate character set diamond @int ACS_HLINE alternate character set horizontal line @int ACS_LANTERN alternate character set lantern @int ACS_LARROW alternate character set left arrow @int ACS_LLCORNER alternate character set lower left corner @int ACS_LRCORNER alternate character set lower right corner @int ACS_LTEE alternate character set left tee @int ACS_PLMINUS alternate character set plus/minus @int ACS_PLUS alternate character set plus @int ACS_RARROW alternate character set right arrow @int ACS_RTEE alternate character set right tee @int ACS_S1 alternate character set scan-line 1 @int ACS_S9 alternate character set scan-line 9 @int ACS_TTEE alternate character set top tee @int ACS_UARROW alternate character set up arrow @int ACS_ULCORNER alternate character set upper left corner @int ACS_URCORNER alternate character set upper right corner @int ACS_VLINE alternate character set vertical line @int A_ALTCHARSET alternatate character set attribute @int A_ATTRIBUTES attributed character attributes bitmask @int A_BLINK blinking attribute @int A_BOLD bold attribute @int A_CHARTEXT attributed character text bitmask @int A_COLOR attributed character color-pair bitmask @int A_DIM half-bright attribute @int A_INVIS invisible attribute @int A_NORMAL normal attribute (all attributes off) @int A_PROTECT protected attribute @int A_REVERSE reverse video attribute @int A_STANDOUT standout attribute @int A_UNDERLINE underline attribute @int COLOR_BLACK black color attribute @int COLOR_BLUE blue color attribute @int COLOR_CYAN cyan color attribute @int COLOR_GREEN green color attribute @int COLOR_MAGENTA magenta color attribute @int COLOR_RED red color attribute @int COLOR_WHITE white color attribute @int COLOR_YELLOW yellow color attribute @int KEY_A1 upper-left of keypad key @int KEY_A3 upper-right of keypad key @int KEY_B2 center of keypad key @int KEY_BACKSPACE backspace key @int KEY_BEG beginning key @int KEY_BREAK break key @int KEY_BTAB backtab key @int KEY_C1 bottom-left of keypad key @int KEY_C3 bottom-right of keypad key @int KEY_CANCEL cancel key @int KEY_CATAB clear all tabs key @int KEY_CLEAR clear screen key @int KEY_CLOSE close key @int KEY_COMMAND command key @int KEY_COPY copy key @int KEY_CREATE create key @int KEY_CTAB clear tab key @int KEY_DC delete character key @int KEY_DL delete line key @int KEY_DOWN down arrow key @int KEY_EIC exit insert char mode key @int KEY_END end key @int KEY_ENTER enter key @int KEY_EOL clear to end of line key @int KEY_EOS clear to end of screen key @int KEY_EXIT exit key @int KEY_F0 f0 key @int KEY_F1 f1 key @int KEY_F2 f2 key @int KEY_F3 f3 key @int KEY_F4 f4 key @int KEY_F5 f5 key @int KEY_F6 f6 key @int KEY_F7 f7 key @int KEY_F8 f8 key @int KEY_F9 f9 key @int KEY_F10 f10 key @int KEY_F11 f11 key @int KEY_F12 f12 key @int KEY_F13 f13 key @int KEY_F14 f14 key @int KEY_F15 f15 key @int KEY_F16 f16 key @int KEY_F17 f17 key @int KEY_F18 f18 key @int KEY_F19 f19 key @int KEY_F20 f20 key @int KEY_F21 f21 key @int KEY_F22 f22 key @int KEY_F23 f23 key @int KEY_F24 f24 key @int KEY_F25 f25 key @int KEY_F26 f26 key @int KEY_F27 f27 key @int KEY_F28 f28 key @int KEY_F29 f29 key @int KEY_F30 f30 key @int KEY_F31 f31 key @int KEY_F32 f32 key @int KEY_F33 f33 key @int KEY_F34 f34 key @int KEY_F35 f35 key @int KEY_F36 f36 key @int KEY_F37 f37 key @int KEY_F38 f38 key @int KEY_F39 f39 key @int KEY_F40 f40 key @int KEY_F41 f41 key @int KEY_F42 f42 key @int KEY_F43 f43 key @int KEY_F44 f44 key @int KEY_F45 f45 key @int KEY_F46 f46 key @int KEY_F47 f47 key @int KEY_F48 f48 key @int KEY_F49 f49 key @int KEY_F50 f50 key @int KEY_F51 f51 key @int KEY_F52 f52 key @int KEY_F53 f53 key @int KEY_F54 f54 key @int KEY_F55 f55 key @int KEY_F56 f56 key @int KEY_F57 f57 key @int KEY_F58 f58 key @int KEY_F59 f59 key @int KEY_F60 f60 key @int KEY_F61 f61 key @int KEY_F62 f62 key @int KEY_F63 f63 key @int KEY_FIND find key @int KEY_HELP help key @int KEY_HOME home key @int KEY_IC enter insert char mode key @int KEY_IL insert line key @int KEY_LEFT cursor left key @int KEY_LL home down or bottom key @int KEY_MARK mark key @int KEY_MESSAGE message key @int KEY_MOUSE mouse event available virtual key @int KEY_MOVE move key @int KEY_NEXT next object key @int KEY_NPAGE next page key @int KEY_OPEN open key @int KEY_OPTIONS options key @int KEY_PPAGE previous page key @int KEY_PREVIOUS prewious object key @int KEY_PRINT print key @int KEY_REDO redo key @int KEY_REFERENCE reference key @int KEY_REFRESH refresh key @int KEY_REPLACE replace key @int KEY_RESET hard reset key @int KEY_RESIZE resize event virtual key @int KEY_RESTART restart key @int KEY_RESUME resume key @int KEY_RIGHT cursor right key @int KEY_SAVE save key @int KEY_SBEG shift beginning key @int KEY_SCANCEL shift cancel key @int KEY_SCOMMAND shift command key @int KEY_SCOPY shift copy key @int KEY_SCREATE shift create key @int KEY_SDC shift delete character key @int KEY_SDL shift delete line key @int KEY_SELECT select key @int KEY_SEND send key @int KEY_SEOL shift clear to end of line key @int KEY_SEXIT shift exit key @int KEY_SF scroll one line forward key @int KEY_SFIND shift find key @int KEY_SHELP shift help key @int KEY_SHOME shift home key @int KEY_SIC shift enter insert mode key @int KEY_SLEFT shift cursor left key @int KEY_SMESSAGE shift message key @int KEY_SMOVE shift move key @int KEY_SNEXT shift next object key @int KEY_SOPTIONS shift options key @int KEY_SPREVIOUS shift previous object key @int KEY_SPRINT shift print key @int KEY_SR scroll one line backward key @int KEY_SREDO shift redo key @int KEY_SREPLACE shift replace key @int KEY_SRESET soft reset key @int KEY_SRIGHT shift cursor right key @int KEY_SRSUME shift resume key @int KEY_SSAVE shift save key @int KEY_SSUSPEND shift suspend key @int KEY_STAB shift tab key @int KEY_SUNDO shift undo key @int KEY_SUSPEND suspend key @int KEY_UNDO undo key @int KEY_UP cursor up key @usage -- Print curses constants supported on this host. for name, value in pairs (require "curses") do if type (value) == "number" then print (name, value) end end */ LUALIB_API int luaopen_curses(lua_State *L) { luaopen_curses_window(L); luaopen_curses_chstr(L); luaL_register(L, "curses", curseslib); init_stdscr(L); return 1; } ================================================ FILE: src/lcurses/curses.lua ================================================ --- Lua bindings for curses local M = curses function M.addch (c) return curses.stdscr():addch(c) end function M.mvaddch (y, x, c) return curses.stdscr():mvaddch(y, x, c) end function M.addstr (s) return curses.stdscr():addstr(s) end function M.mvaddstr (y, x, s) return curses.stdscr():mvaddstr(y, x, s) end function M.attron (a) return curses.stdscr():attron(a) end function M.attroff (a) return curses.stdscr():attroff(a) end function M.attrset (a) return curses.stdscr():attrset(a) end function M.clear () return curses.stdscr():clear() end function M.clrtobot () return curses.stdscr():clrtobot() end function M.clrtoeol () return curses.stdscr():clrtoeol() end function M.getch () return curses.stdscr():getch() end function M.mvgetch (y, x) return curses.stdscr():getch(y, x) end function M.getstr (s) return curses.stdscr():getstr(s) end function M.mvgetstr (y, x, s) return curses.stdscr():mvgetstr(y, x, s) end function M.getyx () return curses.stdscr():getyx() end function M.getmaxyx () return curses.stdscr():getmaxyx() end function M.keypad (b) return curses.stdscr():keypad(b) end function M.move (y,x) return curses.stdscr():move(y,x) end function M.refresh () return curses.stdscr():refresh() end function M.timeout (t) return curses.stdscr():timeout(t) end return M ================================================ FILE: src/lcurses/strlcpy.c ================================================ /* $OpenBSD: strlcpy.c,v 1.4 1999/05/01 18:56:41 millert Exp $ */ /* * Copyright (c) 1998 Todd C. Miller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef LCURSES_STRLCPY_C #define LCURSES_STRLCPY_C 1 #ifndef HAVE_STRLCPY #if defined LIBC_SCCS && ! defined lint static char *rcsid = "$OpenBSD: strlcpy.c,v 1.4 1999/05/01 18:56:41 millert Exp $"; #endif /* LIBC_SCCS and not lint */ #include #include static size_t strlcpy(char *dst, const char *src, size_t siz); /* * Copy src to string dst of size siz. At most siz-1 characters * will be copied. Always NUL terminates (unless siz == 0). * Returns strlen(src); if retval >= siz, truncation occurred. */ static size_t strlcpy(char *dst, const char *src, size_t siz) { register char *d = dst; register const char *s = src; register size_t n = siz; /* Copy as many bytes as will fit */ if (n != 0 && --n != 0) { do { if ((*d++ = *s++) == 0) break; } while (--n != 0); } /* Not enough room in dst, add NUL and traverse rest of src */ if (n == 0) { if (siz != 0) *d = '\0'; /* NUL-terminate dst */ while (*s++) ; } return(s - src - 1); /* count does not include NUL */ } #endif /* !HAVE_STRLCPY */ #endif /* !LCURSES_STRLCPY_C */ ================================================ FILE: src/lcurses/window.c ================================================ /* * Curses binding for Lua 5.1, 5.2 & 5.3. * * (c) Gary V. Vaughan 2013-2017 * (c) Reuben Thomas 2009-2012 * (c) Tiago Dionizio 2004-2007 * * 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. */ /*** Curses Window objects. The methods documented on this page are available on any Curses Window object, such as created by: stdscr = curses.initscr () window = curses.newwin (25, 80, 0, 0) @classmod curses.window */ #include #include "../teliva.h" #include "_helpers.c" #include "chstr.c" static const char *WINDOWMETA = "curses:window"; static void lc_newwin(lua_State *L, WINDOW *nw) { if (nw) { WINDOW **w = lua_newuserdata(L, sizeof(WINDOW*)); luaL_getmetatable(L, WINDOWMETA); lua_setmetatable(L, -2); *w = nw; } else { lua_pushliteral(L, "failed to create window"); lua_error(L); } } static WINDOW ** lc_getwin(lua_State *L, int offset) { WINDOW **w = (WINDOW**)luaL_checkudata(L, offset, WINDOWMETA); if (w == NULL) luaL_argerror(L, offset, "bad curses window"); return w; } static WINDOW * checkwin(lua_State *L, int offset) { WINDOW **w = lc_getwin(L, offset); if (*w == NULL) luaL_argerror(L, offset, "attempt to use closed curses window"); return *w; } /*** Unique string representation of a @{curses.window}. @function __tostring @treturn string unique string representation of the window object. */ static int W__tostring(lua_State *L) { WINDOW **w = lc_getwin(L, 1); char buff[34]; if (*w == NULL) strcpy(buff, "closed"); else sprintf(buff, "%p", lua_touserdata(L, 1)); lua_pushfstring(L, "curses window (%s)", buff); return 1; } /*** Free all the resources associated with a window. @function close @see delwin(3x) */ static int Wclose(lua_State *L) { WINDOW **w = lc_getwin(L, 1); if (*w != NULL && *w != stdscr) { delwin(*w); *w = NULL; } return 0; } /*** Move the position of a window. @function move_window @int y offset frow top of screen @int x offset from left of screen @treturn bool `true`, if successful @see mvwin(3x) */ static int Wmove_window(lua_State *L) { WINDOW *w = checkwin(L, 1); int y = checkint(L, 2); int x = checkint(L, 3); return pushokresult(mvwin(w, y, x)); } /*** Create a new subwindow. @function sub @treturn window a new absolutely positioned subwindow @int nlines number of window lines @int ncols number of window columns @int begin_y top line of window @int begin_x leftmost column of window @see subwin(3x) @see derive */ static int Wsub(lua_State *L) { WINDOW *orig = checkwin(L, 1); int nlines = checkint(L, 2); int ncols = checkint(L, 3); int begin_y = checkint(L, 4); int begin_x = checkint(L, 5); lc_newwin(L, subwin(orig, nlines, ncols, begin_y, begin_x)); return 1; } /*** Create a new derived window. @function derive @int nlines number of window lines @int ncols number of window columns @int begin_y top line of window @int begin_x leftmost column of window @treturn window a new relatively positioned subwindow @see derwin(3x) @see sub */ static int Wderive(lua_State *L) { WINDOW *orig = checkwin(L, 1); int nlines = checkint(L, 2); int ncols = checkint(L, 3); int begin_y = checkint(L, 4); int begin_x = checkint(L, 5); lc_newwin(L, derwin(orig, nlines, ncols, begin_y, begin_x)); return 1; } /*** Move the position of a derived window. @function move_derived @int par_y lines from top of parent window @int par_x columns from left of parent window @treturn bool `true`, if successful @see mvderwin(3x) */ static int Wmove_derived(lua_State *L) { WINDOW *w = checkwin(L, 1); int par_y = checkint(L, 2); int par_x = checkint(L, 3); return pushokresult(mvderwin(w, par_y, par_x)); } /*** Change the size of a window. @function resize @int height new number of lines @int width new number of columns @treturn bool `true`, if successful @fixme ncurses only? */ static int Wresize(lua_State *L) { WINDOW *w = checkwin(L, 1); int height = checkint(L, 2); int width = checkint(L, 3); int c = wresize(w, height, width); if (c == ERR) return 0; return pushokresult(true); } /*** Make a duplicate of a window. @function clone @treturn window a new duplicate of this window @see dupwin(3x) */ static int Wclone(lua_State *L) { WINDOW *w = checkwin(L, 1); lc_newwin(L, dupwin(w)); return 1; } /*** Mark ancestors of a window for refresh. @function syncup @see wsyncup(3x) */ static int Wsyncup(lua_State *L) { wsyncup(checkwin(L, 1)); return 0; } /*** Automatically mark ancestors of a changed window for refresh. @function syncok @bool bf @treturn bool `true`, if successful @fixme ncurses only */ static int Wsyncok(lua_State *L) { WINDOW *w = checkwin(L, 1); int bf = lua_toboolean(L, 2); return pushokresult(syncok(w, bf)); } /*** Mark cursor position of ancestors of a window for refresh. @function cursyncup @see wcursyncup(3x) */ static int Wcursyncup(lua_State *L) { wcursyncup(checkwin(L, 1)); return 0; } /*** Mark child windows for refresh. @function syncdown @see syncdown(3x) @see refresh */ static int Wsyncdown(lua_State *L) { wsyncdown(checkwin(L, 1)); return 0; } /*** Refresh the window terminal display from the virtual screen. @function refresh @treturn bool `true`, if successful @see wrefresh(3x) @see curses.doupdate @see noutrefresh */ static int Wrefresh(lua_State *L) { int result = wrefresh(checkwin(L, 1)); render_trusted_teliva_data(L); return pushokresult(result); } /*** Copy the window backing screen to the virtual screen. @function noutrefresh @treturn bool `true`, if successful @see wnoutrefresh(3x) @see curses.doupdate @see refresh */ static int Wnoutrefresh(lua_State *L) { return pushokresult(wnoutrefresh(checkwin(L, 1))); } /*** Mark a window as having corrupted display that needs fully redrawing. @function redrawwin @treturn bool `true`, if successful @see redrawwin(3x) @see redrawln */ static int Wredrawwin(lua_State *L) { return pushokresult(redrawwin(checkwin(L, 1))); } /*** Mark a range of lines in a window as corrupted and in need of redrawing. @function redrawln @int beg_line @int num_lines @treturn bool `true`, if successful @see wredrawln(3x) */ static int Wredrawln(lua_State *L) { WINDOW *w = checkwin(L, 1); int beg_line = checkint(L, 2); int num_lines = checkint(L, 3); return pushokresult(wredrawln(w, beg_line, num_lines)); } /*** Change the cursor position. @function move @int y @int x @treturn bool `true`, if successful @see wmove(3x) */ static int Wmove(lua_State *L) { WINDOW *w = checkwin(L, 1); int y = checkint(L, 2); int x = checkint(L, 3); return pushokresult(wmove(w, y, x)); } /*** Scroll the window up *n* lines. @function scrl @int n @treturn bool `true`, if successful @see wscrl(3x) */ static int Wscrl(lua_State *L) { WINDOW *w = checkwin(L, 1); int n = checkint(L, 2); return pushokresult(wscrl(w, n)); } /*** Set the changed state of a window since the last refresh. @function touch @param[opt] changed @treturn bool `true`, if successful @see touchwin(3x) */ static int Wtouch(lua_State *L) { WINDOW *w = checkwin(L, 1); int changed; if (lua_isnoneornil(L, 2)) changed = TRUE; else changed = lua_toboolean(L, 2); if (changed) return pushokresult(touchwin(w)); return pushokresult(untouchwin(w)); } /*** Mark a range of lines as changed since the last refresh. @function touchline @int y @int n @param[opt] changed @treturn bool `true`, if successful @see wtouchln(3x) */ static int Wtouchline(lua_State *L) { WINDOW *w = checkwin(L, 1); int y = checkint(L, 2); int n = checkint(L, 3); int changed; if (lua_isnoneornil(L, 4)) changed = TRUE; else changed = lua_toboolean(L, 4); return pushokresult(wtouchln(w, y, n, changed)); } /*** Has a particular window line changed since the last refresh? @function is_linetouched @int line @treturn bool `true`, if successful @see is_linetouched(3x) */ static int Wis_linetouched(lua_State *L) { WINDOW *w = checkwin(L, 1); int line = checkint(L, 2); return pushboolresult(is_linetouched(w, line)); } /*** Has a window changed since the last refresh? @function is_wintouched @treturn bool `true`, if successful @see is_wintouched(3x) */ static int Wis_wintouched(lua_State *L) { WINDOW *w = checkwin(L, 1); return pushboolresult(is_wintouched(w)); } /*** Fetch the cursor position. @function getyx @treturn int y coordinate of top line @treturn int x coordinate of left column @see getyx(3x) */ static int Wgetyx(lua_State *L) { WINDOW *w = checkwin(L, 1); int y, x; getyx(w, y, x); lua_pushinteger(L, y); lua_pushinteger(L, x); return 2; } /*** Fetch the parent-relative coordinates of a subwindow. @function getparyx @treturn int y coordinate of top line relative to parent window @treturn int x coordinate of left column relative to parent window @see getparyx(3x) */ static int Wgetparyx(lua_State *L) { WINDOW *w = checkwin(L, 1); int y, x; getparyx(w, y, x); lua_pushinteger(L, y); lua_pushinteger(L, x); return 2; } /*** Fetch the absolute top-left coordinates of a window. @function getbegyx @treturn int y coordinate of top line @treturn int x coordinate of left column @see getbegyx(3x) */ static int Wgetbegyx(lua_State *L) { WINDOW *w = checkwin(L, 1); int y, x; getbegyx(w, y, x); lua_pushinteger(L, y); lua_pushinteger(L, x); return 2; } /*** Fetch the absolute bottom-right coordinates of a window. @function getmaxyx @treturn int y coordinate of bottom line @treturn int x coordinate of right column @see getmaxyx(3x) */ static int Wgetmaxyx(lua_State *L) { WINDOW *w = checkwin(L, 1); int y, x; getmaxyx(w, y, x); --y; // set aside space for the menu bar lua_pushinteger(L, y); lua_pushinteger(L, x); return 2; } /*** Draw a border around a window. @function border @int[opt] ls @int[opt] rs @int[opt] ts @int[opt] bs @int[opt] tl @int[opt] tr @int[opt] bl @int[opt] br @treturn bool `true`, if successful @see wborder(3x) @usage win:border (curses.ACS_VLINE, curses.ACS_VLINE, curses.ACS_HLINE, curses.ACS_HLINE, curses.ACS_ULCORNER, curses.ACS_URCORNER, curses.ACS_LLCORNER, curses.ACS_LRCORNER) */ static int Wborder(lua_State *L) { WINDOW *w = checkwin(L, 1); chtype ls = optch(L, 2, 0); chtype rs = optch(L, 3, 0); chtype ts = optch(L, 4, 0); chtype bs = optch(L, 5, 0); chtype tl = optch(L, 6, 0); chtype tr = optch(L, 7, 0); chtype bl = optch(L, 8, 0); chtype br = optch(L, 9, 0); return pushokresult(wborder(w, ls, rs, ts, bs, tl, tr, bl, br)); } /*** Draw a box around a window. @function box @int verch @int horch @treturn bool `true`, if successful @see box(3x) @see border @usage win:box (curses.ACS_VLINE, curses.ACS_HLINE) */ static int Wbox(lua_State *L) { WINDOW *w = checkwin(L, 1); chtype verch = checkch(L, 2); chtype horch = checkch(L, 3); return pushokresult(box(w, verch, horch)); } /*** Draw a row of characters from the current cursor position. @function hline @int ch @int n @treturn bool `true`, if successful @see whline(3x) @see mvhline @see vline @usage _, width = win:getmaxyx () win:hline (curses.ACS_HLINE, width - curs_x) */ static int Whline(lua_State *L) { WINDOW *w = checkwin(L, 1); chtype ch = checkch(L, 2); int n = checkint(L, 3); return pushokresult(whline(w, ch, n)); } /*** Draw a column of characters from the current cursor position. @function vline @int ch @int n @treturn bool `true`, if successful @see wvline(3x) @see hline @see mvvline */ static int Wvline(lua_State *L) { WINDOW *w = checkwin(L, 1); chtype ch = checkch(L, 2); int n = checkint(L, 3); return pushokresult(wvline(w, ch, n)); } /*** Move the cursor, then draw a row of characters from the new cursor position. @function mvhline @int y @int x @int ch @int n @treturn bool `true`, if successful @see mvwhline(3x) */ static int Wmvhline(lua_State *L) { WINDOW *w = checkwin(L, 1); int y = checkint(L, 2); int x = checkint(L, 3); chtype ch = checkch(L, 4); int n = checkint(L, 5); return pushokresult(mvwhline(w, y, x, ch, n)); } /*** Move the cursor, then draw a column of characters from the new cursor position. @function mvvline @int y @int x @int ch @int n @treturn bool `true`, if successful @see mvwvline(3x) */ static int Wmvvline(lua_State *L) { WINDOW *w = checkwin(L, 1); int y = checkint(L, 2); int x = checkint(L, 3); chtype ch = checkch(L, 4); int n = checkint(L, 5); return pushokresult(mvwvline(w, y, x, ch, n)); } /*** Write blanks to every character position in the window. @function erase @treturn bool `true`, if successful @see werase(3x) */ static int Werase(lua_State *L) { return pushokresult(werase(checkwin(L, 1))); } /*** Call @{erase} and then @{clearok}. @function clear @treturn bool `true`, if successful @see wclear(3x) */ static int Wclear(lua_State *L) { return pushokresult(wclear(checkwin(L, 1))); } /*** Write blanks to every character position after the cursor. @function clrtobot @treturn bool `true`, if successful @see wclrtobot(3x) */ static int Wclrtobot(lua_State *L) { return pushokresult(wclrtobot(checkwin(L, 1))); } /*** Write blanks from the cursor to the end of the current line. @function clrtoeol @treturn bool `true`, if successful @see wclrtoeol(3x) */ static int Wclrtoeol(lua_State *L) { return pushokresult(wclrtoeol(checkwin(L, 1))); } /*** Advance the cursor after writing a character at the old position. @function addch @int ch @treturn bool `true`, if successful @see waddch(3x) */ static int Waddch(lua_State *L) { WINDOW *w = checkwin(L, 1); chtype ch = checkch(L, 2); return pushokresult(waddch(w, ch)); } /*** Call @{move}, then @{addch}. @function mvaddch @int y @int x @int ch @treturn bool `true`, if successful @see mvwaddch(3x) */ static int Wmvaddch(lua_State *L) { WINDOW *w = checkwin(L, 1); int y = checkint(L, 2); int x = checkint(L, 3); chtype ch = checkch(L, 4); return pushokresult(mvwaddch(w, y, x, ch)); } /*** Call @{addch} then @{refresh}. @function echoch @int ch @treturn bool `true`, if successful @see wechochar(3x) */ static int Wechoch(lua_State *L) { WINDOW *w = checkwin(L, 1); chtype ch = checkch(L, 2); return pushokresult(wechochar(w, ch)); } /*** Copy a @{curses.chstr} starting at the current cursor position. @function addchstr @int chstr cs @int[opt] n @treturn bool `true`, if successful @see waddchnstr(3x) */ static int Waddchstr(lua_State *L) { WINDOW *w = checkwin(L, 1); int n = optint(L, 3, -1); chstr *cs = checkchstr(L, 2); if (n < 0 || n > (int) cs->len) n = cs->len; return pushokresult(waddchnstr(w, cs->str, n)); } /*** Call @{move} then @{addchstr}. @function mvaddchstr @int y @int x @int[opt] n @treturn bool `true`, if successful @see mvwaddchnstr(3x) */ static int Wmvaddchstr(lua_State *L) { WINDOW *w = checkwin(L, 1); int y = checkint(L, 2); int x = checkint(L, 3); int n = optint(L, 5, -1); chstr *cs = checkchstr(L, 4); if (n < 0 || n > (int) cs->len) n = cs->len; return pushokresult(mvwaddchnstr(w, y, x, cs->str, n)); } /*** Copy a Lua string starting at the current cursor position. @function addstr @string str @int[opt] n @treturn bool `true`, if successful @see waddnstr(3x) */ static int Waddstr(lua_State *L) { WINDOW *w = checkwin(L, 1); const char *str = luaL_checkstring(L, 2); int n = optint(L, 3, -1); return pushokresult(waddnstr(w, str, n)); } /*** Call @{move} then @{addstr}. @function mvaddstr @int y @int x @string str @int[opt] n @treturn bool `true`, if successful @see mvwaddnstr(3x) */ static int Wmvaddstr(lua_State *L) { WINDOW *w = checkwin(L, 1); int y = checkint(L, 2); int x = checkint(L, 3); const char *str = luaL_checkstring(L, 4); int n = optint(L, 5, -1); return pushokresult(mvwaddnstr(w, y, x, str, n)); } /*** Set the background attributes for subsequently written characters. @function wbkgdset @int ch @see wbkgdset(3x) */ static int Wwbkgdset(lua_State *L) { WINDOW *w = checkwin(L, 1); chtype ch = checkch(L, 2); wbkgdset(w, ch); return 0; } /*** Call @{wbkgdset} and then set the background of every position accordingly. @function wbkgd @int ch @treturn bool `true`, if successful @see wbkgd(3x) */ static int Wwbkgd(lua_State *L) { WINDOW *w = checkwin(L, 1); chtype ch = checkch(L, 2); return pushokresult(wbkgd(w, ch)); } /*** Fetch the current background attribute for the window. @function getbkgd @treturn int current window background attribute @see getbkgd(3x) @see wbkgd */ static int Wgetbkgd(lua_State *L) { return pushintresult(getbkgd(checkwin(L, 1))); } /*** Set the flush on interrupt behaviour for the window. @function intrflush @bool bf @treturn bool `true`, if successful @see intrflush(3x) */ static int Wintrflush(lua_State *L) { WINDOW *w = checkwin(L, 1); int bf = lua_toboolean(L, 2); return pushokresult(intrflush(w, bf)); } /*** Set the single value keypad keys behaviour for the window. @function keypad @bool[opt] on @treturn bool `true`, if successful @see keypad(3x) */ static int Wkeypad(lua_State *L) { WINDOW *w = checkwin(L, 1); int bf = lua_isnoneornil(L, 2) ? 1 : lua_toboolean(L, 2); return pushokresult(keypad(w, bf)); } /*** Force 8-bit (or 7-bit) input characters for the window. @function meta @bool on `true` to force 8-bit input characters @treturn bool `true`, if successful @see meta(3x) */ static int Wmeta(lua_State *L) { WINDOW *w = checkwin(L, 1); int bf = lua_toboolean(L, 2); return pushokresult(meta(w, bf)); } /*** Force @{getch} to be non-blocking. @function nodelay @bool on @treturn bool `true`, if successful @see nodelay(3x) */ static int Wnodelay(lua_State *L) { WINDOW *w = checkwin(L, 1); int bf = lua_toboolean(L, 2); return pushokresult(nodelay(w, bf)); } /*** For differentiating user input from terminal control sequences. @function timeout @int delay milliseconds, `0` for blocking, `-1` for non-blocking @see wtimeout(3x) */ static int Wtimeout(lua_State *L) { WINDOW *w = checkwin(L, 1); int delay = checkint(L, 2); wtimeout(w, delay); return 0; } /*** Return input immediately from this window. @function notimeout @bool bf @treturn bool `true`, if successful @fixme ncurses only? */ static int Wnotimeout(lua_State *L) { WINDOW *w = checkwin(L, 1); int bf = lua_toboolean(L, 2); return pushokresult(notimeout(w, bf)); } /*** The next call to @{refresh} will clear and completely redraw the window. @function clearok @bool bf @treturn bool `true`, if successful @see clearok(3x) */ static int Wclearok(lua_State *L) { WINDOW *w = checkwin(L, 1); int bf = lua_toboolean(L, 2); return pushokresult(clearok(w, bf)); } /*** Use hardware character insert and delete on supporting terminals. @function idcok @bool bf @treturn bool `true`, if successful @see idcok(3x) */ static int Widcok(lua_State *L) { WINDOW *w = checkwin(L, 1); int bf = lua_toboolean(L, 2); idcok(w, bf); return 0; } /*** Use hardware line insert and delete on supporting terminals. @function idlok @bool bf @treturn bool `true`, if successful @see idlok(3x) */ static int Widlok(lua_State *L) { WINDOW *w = checkwin(L, 1); int bf = lua_toboolean(L, 2); return pushokresult(idlok(w, bf)); } /*** No need to force synchronisation of hardware cursor. @function leaveok @bool bf @treturn bool `true`, if successful @see leaveok(3x) */ static int Wleaveok(lua_State *L) { WINDOW *w = checkwin(L, 1); int bf = lua_toboolean(L, 2); return pushokresult(leaveok(w, bf)); } /*** Scroll up one line when the cursor writes to the last screen position. @function scrollok @bool bf @treturn bool `true`, if successful @see scrollok(3x) */ static int Wscrollok(lua_State *L) { WINDOW *w = checkwin(L, 1); int bf = lua_toboolean(L, 2); return pushokresult(scrollok(w, bf)); } /*** Automatically call @{refresh} whenever the window content is changed. @function immedok @bool bf @treturn bool `true`, if successful @see immedok(3x) */ static int Wimmedok(lua_State *L) { WINDOW *w = checkwin(L, 1); int bf = lua_toboolean(L, 2); immedok(w, bf); return 0; } /*** Set a software scrolling region for the window. @function wsetscrreg @int top top line of the scrolling region (line 0 is the first line) @int bot bottom line of the scrolling region @treturn bool `true`, if successful @see wsetscrreg(3x) */ static int Wwsetscrreg(lua_State *L) { WINDOW *w = checkwin(L, 1); int top = checkint(L, 2); int bot = checkint(L, 3); return pushokresult(wsetscrreg(w, top, bot)); } /*** Overlay this window on top of another non-destructively. @function overlay @tparam window dst destination window @treturn bool `true`, if successful @see overlay(3x) @see overwrite */ static int Woverlay(lua_State *L) { WINDOW *srcwin = checkwin(L, 1); WINDOW *dstwin = checkwin(L, 2); return pushokresult(overlay(srcwin, dstwin)); } /*** Destructively overwrite another window with this one. @function overwrite @tparam window dst destination window @treturn bool `true`, if successful @see overwrite(3x) */ static int Woverwrite(lua_State *L) { WINDOW *srcwin = checkwin(L, 1); WINDOW *dstwin = checkwin(L, 2); return pushokresult(overwrite(srcwin, dstwin)); } /*** Overlay a rectangle of this window over another. @function copywin @tparam window dst destination window @int st top row from this window @int sl left column from this window @int dt top row of rectangle @int dl left column of rectangle @int db bottom row of rectangle @int dr right column of rectangle @bool overlay if `true`, copy destructively like @{overlay} @treturn bool `true`, if successful @see copywin(3x) */ static int Wcopywin(lua_State *L) { WINDOW *srcwin = checkwin(L, 1); WINDOW *dstwin = checkwin(L, 2); int sminrow = checkint(L, 3); int smincol = checkint(L, 4); int dminrow = checkint(L, 5); int dmincol = checkint(L, 6); int dmaxrow = checkint(L, 7); int dmaxcol = checkint(L, 8); int woverlay = lua_toboolean(L, 9); return pushokresult(copywin(srcwin, dstwin, sminrow, smincol, dminrow, dmincol, dmaxrow, dmaxcol, woverlay)); } /*** Delete the character under the cursor. @function delch @treturn bool `true`, if successful @see wdelch(3x) */ static int Wdelch(lua_State *L) { return pushokresult(wdelch(checkwin(L, 1))); } /*** Call @{move} then @{delch}. @function mvdelch @int y @int x @treturn bool `true`, if successful @see mvwdelch(3x) */ static int Wmvdelch(lua_State *L) { WINDOW *w = checkwin(L, 1); int y = checkint(L, 2); int x = checkint(L, 3); return pushokresult(mvwdelch(w, y, x)); } /*** Move the lines below the cursor up, to delete the current line. @function deleteln @treturn bool `true`, if successful @see wdeleteln(3x) */ static int Wdeleteln(lua_State *L) { return pushokresult(wdeleteln(checkwin(L, 1))); } /*** Move the current line and those below down one line, leaving a new blank line. @function insertln @treturn bool `true`, if successful @see wdeleteln(3x) */ static int Winsertln(lua_State *L) { return pushokresult(winsertln(checkwin(L, 1))); } /*** Call @{deleteln} *n* times. @function winsdelln @int n @treturn bool `true`, if successful @see winsdelln(3x) */ static int Wwinsdelln(lua_State *L) { WINDOW *w = checkwin(L, 1); int n = checkint(L, 2); return pushokresult(winsdelln(w, n)); } /*** Read a character from the window input. @function getch @treturn int character read from input buffer @see wgetch(3x) @see curses.cbreak @see curses.echo @see keypad */ static int Wgetch(lua_State *L) { WINDOW *w = checkwin(L, 1); int y, x; getyx(w, y, x); render_trusted_teliva_data(L); /* Apps can draw what they want on screen, * but Teliva's UI is always visible when * asking the user to make a decision. */ if (x > COLS-2) x = COLS-2; if (y > LINES-1) y = LINES-1; /* http://gnats.netbsd.org/56664 */ mvaddstr(y, x, ""); int c = wgetch(w); /* audit log */ static char buffer[1024] = {0}; memset(buffer, '\0', 1024); if (isspace(c)) snprintf(buffer, 1020, "getch() => %s", character_name(c)); else snprintf(buffer, 1020, "getch() => %c", c); append_to_audit_log(L, buffer); if (c == ERR) return 0; /* standard menu hotkeys */ if (c == CTRL_U && editor_view_in_progress(L)) developer_mode(L); else if (c == CTRL_X || c == CTRL_U || c == CTRL_P) { /* always confirm; we're going to throw away data past this point */ /* draw a special menu just for this situation */ attron(A_BOLD|A_REVERSE); color_set(COLOR_PAIR_MENU, NULL); for (int x = 0; x < COLS; ++x) mvaddch(LINES-1, x, ' '); menu_column = 2; if (c == CTRL_X) draw_menu_item("^x", "exit"); else if (c == CTRL_U) draw_menu_item("^u", "edit app code"); else if (c == CTRL_P) draw_menu_item("^p", "modify app permissions"); draw_menu_item("anything else", "cancel"); color_set(COLOR_PAIR_ERROR, NULL); mvaddstr(LINES-1, menu_column+1, " Please confirm. App will restart, losing unsaved data. "); color_set(COLOR_PAIR_NORMAL, NULL); attroff(A_BOLD|A_REVERSE); int secondc; do /* just in case getch is currently non-blocking (nodelay) */ secondc = wgetch(w); while(secondc == ERR); if (c != secondc) return pushintresult(0); if (c == CTRL_X) { unlink("teliva_editor_state"); exit(0); } if (c == CTRL_U) developer_mode(L); if (c == CTRL_P) permissions_mode(L); } return pushintresult(c); } /*** Put back a character obtained from @{getch} @function ungetch @int ch @treturn OK or ERR @see mvwgetch(3x) @see getch */ static int Wungetch(lua_State *L) { int ch = checkint(L, 2); int result = ungetch(ch); if (result == ERR) return 0; return pushintresult(result); } /*** Call @{move} then @{getch} @function mvgetch @int y @int x @treturn int character read from input buffer @see mvwgetch(3x) @see getch */ static int Wmvgetch(lua_State *L) { WINDOW *w = checkwin(L, 1); int y = checkint(L, 2); int x = checkint(L, 3); int c; if (wmove(w, y, x) == ERR) return 0; c = wgetch(w); if (c == ERR) return 0; return pushintresult(c); } /*** Fetch the attributed character at the current cursor position. @function winch @treturn int attributed character read from input buffer @see winch(3x) */ static int Wwinch(lua_State *L) { WINDOW *w = checkwin(L, 1); return pushintresult(winch(w)); } /*** Call @{move} then @{winch} @function mvwinch @int y @int x @treturn int attributed character read from input buffer @see mvwinch(3x) */ static int Wmvwinch(lua_State *L) { WINDOW *w = checkwin(L, 1); int y = checkint(L, 2); int x = checkint(L, 3); return pushintresult(mvwinch(w, y, x)); } /*** Fetch attributed characters from cursor position up to rightmost window position. @function winchnstr @int n @treturn curses.chstr characters from cursor to end of line @see winchnstr(3x) @see winnstr */ static int Wwinchnstr(lua_State *L) { WINDOW *w = checkwin(L, 1); int n = checkint(L, 2); chstr *cs = chstr_new(L, n); if (winchnstr(w, cs->str, n) == ERR) return 0; return 1; } /*** Call @{move} then @{winchnstr}. @function mvwinchnstr @int y @int x @int n @treturn curses.chstr characters from cursor to end of line @see mvwinchnstr(3x) */ static int Wmvwinchnstr(lua_State *L) { WINDOW *w = checkwin(L, 1); int y = checkint(L, 2); int x = checkint(L, 3); int n = checkint(L, 4); chstr *cs = chstr_new(L, n); if (mvwinchnstr(w, y, x, cs->str, n) == ERR) return 0; return 1; } /*** Fetch a string from cursor position up to rightmost window position. @function winnstr @int n @treturn string string read from input buffer @see winnstr(3x) @see winchnstr */ static int Wwinnstr(lua_State *L) { WINDOW *w = checkwin(L, 1); int n = checkint(L, 2); char buf[LUAL_BUFFERSIZE]; char *ptr = buf; if (abs(n) >= LUAL_BUFFERSIZE) ptr = lua_newuserdata(L, (size_t) abs(n) + 1); n = winnstr(w, ptr, n); if (n == ERR) return 0; lua_pushlstring(L, ptr, n); return 1; } /*** Call @{move} then @{winnstr}. @function mvwinnstr @int y @int x @int n @treturn string string read from input buffer @see mvwinnstr(3x) */ static int Wmvwinnstr(lua_State *L) { WINDOW *w = checkwin(L, 1); int y = checkint(L, 2); int x = checkint(L, 3); int n = checkint(L, 4); char buf[LUAL_BUFFERSIZE]; char *ptr = buf; if (abs(n) >= LUAL_BUFFERSIZE) ptr = lua_newuserdata(L, (size_t) abs(n) + 1); n = mvwinnstr(w, y, x, ptr, n); if (n == ERR) return 0; lua_pushlstring(L, ptr, n); return 1; } /*** Insert an attributed character before the current cursor position. @function winsch @int ch @treturn bool `true`, if successful @see winsch(3x) */ static int Wwinsch(lua_State *L) { WINDOW *w = checkwin(L, 1); chtype ch = checkch(L, 2); return pushokresult(winsch(w, ch)); } /*** Call @{move} then @{winsch}. @function mvwinsch @int y @int x @int ch @treturn bool `true`, if successful @see mvwinsch(3x) */ static int Wmvwinsch(lua_State *L) { WINDOW *w = checkwin(L, 1); int y = checkint(L, 2); int x = checkint(L, 3); chtype ch = checkch(L, 4); return pushokresult(mvwinsch(w, y, x, ch)); } /*** Insert a string of characters before the current cursor position. @function winsstr @string str @treturn bool `true`, if successful @see winsstr(3x) */ static int Wwinsstr(lua_State *L) { WINDOW *w = checkwin(L, 1); const char *str = luaL_checkstring(L, 2); return pushokresult(winsnstr(w, str, lua_strlen(L, 2))); } /*** Call @{move} then @{winsstr}. @function mvwinsstr @int y @int x @string str @treturn bool `true`, if successful @see mvwinsstr(3x) */ static int Wmvwinsstr(lua_State *L) { WINDOW *w = checkwin(L, 1); int y = checkint(L, 2); int x = checkint(L, 3); const char *str = luaL_checkstring(L, 4); return pushokresult(mvwinsnstr(w, y, x, str, lua_strlen(L, 2))); } /*** Like @{winsstr}, but no more than *n* characters. @function winsnstr @string str @int n @treturn bool `true`, if successful @see winsnstr(3x) */ static int Wwinsnstr(lua_State *L) { WINDOW *w = checkwin(L, 1); const char *str = luaL_checkstring(L, 2); int n = checkint(L, 3); return pushokresult(winsnstr(w, str, n)); } /*** Call @{move} then @{winsnstr}. @function mvwinsnstr @int y @int x @string str @int n @treturn bool `true`, if successful @see mvwinsnstr(3x) */ static int Wmvwinsnstr(lua_State *L) { WINDOW *w = checkwin(L, 1); int y = checkint(L, 2); int x = checkint(L, 3); const char *str = luaL_checkstring(L, 4); int n = checkint(L, 5); return pushokresult(mvwinsnstr(w, y, x, str, n)); } /*** Return a new subpad window object. @function subpad @int nlines @int ncols @int begin_y @int begin_x @treturn window a new subpad window object @see subpad(3x) */ static int Wsubpad(lua_State *L) { WINDOW *orig = checkwin(L, 1); int nlines = checkint(L, 2); int ncols = checkint(L, 3); int begin_y = checkint(L, 4); int begin_x = checkint(L, 5); lc_newwin(L, subpad(orig, nlines, ncols, begin_y, begin_x)); return 1; } /*** Equivalent to @{refresh} for use with pad windows. @function prefresh @int st top row from this pad window @int sl left column from this pad window @int dt top row of rectangle @int dl left column of rectangle @int db bottom row of rectangle @int dr right column of rectangle @treturn bool `true`, if successful @see prefresh(3x) */ static int Wprefresh(lua_State *L) { WINDOW *p = checkwin(L, 1); int pminrow = checkint(L, 2); int pmincol = checkint(L, 3); int sminrow = checkint(L, 4); int smincol = checkint(L, 5); int smaxrow = checkint(L, 6); int smaxcol = checkint(L, 7); return pushokresult(prefresh(p, pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol)); } /*** Equivalent to @{noutrefresh} for use with pad windows. @function pnoutrefresh @int st top row from this pad window @int sl left column from this pad window @int dt top row of rectangle @int dl left column of rectangle @int db bottom row of rectangle @int dr right column of rectangle @treturn bool `true`, if successful @see pnoutrefresh(3x) */ static int Wpnoutrefresh(lua_State *L) { WINDOW *p = checkwin(L, 1); int pminrow = checkint(L, 2); int pmincol = checkint(L, 3); int sminrow = checkint(L, 4); int smincol = checkint(L, 5); int smaxrow = checkint(L, 6); int smaxcol = checkint(L, 7); return pushokresult(pnoutrefresh(p, pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol)); } /*** An efficient equivalent to @{addch} followed by @{refresh}. @function pechochar @int ch @treturn bool `true`, if successful @see pechochar(3x) */ static int Wpechochar(lua_State *L) { WINDOW *p = checkwin(L, 1); chtype ch = checkch(L, 2); return pushokresult(pechochar(p, ch)); } /*** Turn off the given attributes for subsequent writes to the window. @function attroff @int attrs @treturn bool `true`, if successful @see wattroff(3x) @see standend */ static int Wattroff(lua_State *L) { WINDOW *w = checkwin(L, 1); int attrs = checkint(L, 2); return pushokresult(wattroff(w, attrs)); } /*** Turn on the given attributes for subsequent writes to the window. @function attron @int attrs @treturn bool `true`, if successful @see wattron(3x) */ static int Wattron(lua_State *L) { WINDOW *w = checkwin(L, 1); int attrs = checkint(L, 2); return pushokresult(wattron(w, attrs)); } /*** Set the given attributes for subsequent writes to the window. @function attrset @int attrs @treturn bool `true`, if successful @see wattrset(3x) */ static int Wattrset(lua_State *L) { WINDOW *w = checkwin(L, 1); int attrs = checkint(L, 2); return pushokresult(wattrset(w, attrs)); } /*** Turn off all attributes for subsequent writes to the window. @function standend @treturn bool `true`, if successful @see wstandend(3x) */ static int Wstandend(lua_State *L) { return pushokresult(wstandend(checkwin(L, 1))); } /*** Set `A_STANDOUT` for subsequent writes to the window. @function standout @treturn bool `true`, if successful @see wstandout(3x) */ static int Wstandout(lua_State *L) { return pushokresult(wstandout(checkwin(L, 1))); } static const luaL_Reg curses_window_fns[] = { LCURSES_FUNC( W__tostring ), LCURSES_FUNC( Waddch ), LCURSES_FUNC( Waddchstr ), LCURSES_FUNC( Waddstr ), LCURSES_FUNC( Wattroff ), LCURSES_FUNC( Wattron ), LCURSES_FUNC( Wattrset ), LCURSES_FUNC( Wborder ), LCURSES_FUNC( Wbox ), LCURSES_FUNC( Wclear ), LCURSES_FUNC( Wclearok ), LCURSES_FUNC( Wclone ), LCURSES_FUNC( Wclose ), LCURSES_FUNC( Wclrtobot ), LCURSES_FUNC( Wclrtoeol ), LCURSES_FUNC( Wcopywin ), LCURSES_FUNC( Wcursyncup ), LCURSES_FUNC( Wdelch ), LCURSES_FUNC( Wdeleteln ), LCURSES_FUNC( Wderive ), LCURSES_FUNC( Wechoch ), LCURSES_FUNC( Werase ), LCURSES_FUNC( Wgetbegyx ), LCURSES_FUNC( Wgetbkgd ), LCURSES_FUNC( Wgetch ), LCURSES_FUNC( Wgetmaxyx ), LCURSES_FUNC( Wgetparyx ), /* no 'getstr' because there's no way to hook standard Teliva hotkeys into it */ LCURSES_FUNC( Wgetyx ), LCURSES_FUNC( Whline ), LCURSES_FUNC( Widcok ), LCURSES_FUNC( Widlok ), LCURSES_FUNC( Wimmedok ), LCURSES_FUNC( Winsertln ), LCURSES_FUNC( Wintrflush ), LCURSES_FUNC( Wis_linetouched ), LCURSES_FUNC( Wis_wintouched ), LCURSES_FUNC( Wkeypad ), LCURSES_FUNC( Wleaveok ), LCURSES_FUNC( Wmeta ), LCURSES_FUNC( Wmove ), LCURSES_FUNC( Wmove_derived ), LCURSES_FUNC( Wmove_window ), LCURSES_FUNC( Wmvaddch ), LCURSES_FUNC( Wmvaddchstr ), LCURSES_FUNC( Wmvaddstr ), LCURSES_FUNC( Wmvdelch ), LCURSES_FUNC( Wmvgetch ), /* no 'mvgetstr' because there's no way to hook standard Teliva hotkeys into it */ LCURSES_FUNC( Wmvhline ), LCURSES_FUNC( Wmvvline ), LCURSES_FUNC( Wmvwinch ), LCURSES_FUNC( Wmvwinchnstr ), LCURSES_FUNC( Wmvwinnstr ), LCURSES_FUNC( Wmvwinsch ), LCURSES_FUNC( Wmvwinsnstr ), LCURSES_FUNC( Wmvwinsstr ), LCURSES_FUNC( Wnodelay ), LCURSES_FUNC( Wnotimeout ), LCURSES_FUNC( Wnoutrefresh ), LCURSES_FUNC( Woverlay ), LCURSES_FUNC( Woverwrite ), LCURSES_FUNC( Wpechochar ), LCURSES_FUNC( Wpnoutrefresh ), LCURSES_FUNC( Wprefresh ), LCURSES_FUNC( Wredrawln ), LCURSES_FUNC( Wredrawwin ), LCURSES_FUNC( Wrefresh ), LCURSES_FUNC( Wresize ), LCURSES_FUNC( Wscrl ), LCURSES_FUNC( Wscrollok ), LCURSES_FUNC( Wstandend ), LCURSES_FUNC( Wstandout ), LCURSES_FUNC( Wsub ), LCURSES_FUNC( Wsubpad ), LCURSES_FUNC( Wsyncdown ), LCURSES_FUNC( Wsyncok ), LCURSES_FUNC( Wsyncup ), LCURSES_FUNC( Wtimeout ), LCURSES_FUNC( Wtouch ), LCURSES_FUNC( Wtouchline ), LCURSES_FUNC( Wungetch ), LCURSES_FUNC( Wvline ), LCURSES_FUNC( Wwbkgd ), LCURSES_FUNC( Wwbkgdset ), LCURSES_FUNC( Wwinch ), LCURSES_FUNC( Wwinchnstr ), LCURSES_FUNC( Wwinnstr ), LCURSES_FUNC( Wwinsch ), LCURSES_FUNC( Wwinsdelln ), LCURSES_FUNC( Wwinsnstr ), LCURSES_FUNC( Wwinsstr ), LCURSES_FUNC( Wwsetscrreg ), {"__gc", Wclose }, /* rough safety net */ {NULL, NULL} }; LUALIB_API int luaopen_curses_window(lua_State *L) { int t, mt; luaL_register(L, "curses.window", curses_window_fns); t = lua_gettop(L); luaL_newmetatable(L, WINDOWMETA); mt = lua_gettop(L); lua_pushvalue(L, mt); lua_setfield(L, mt, "__index"); /* mt.__index = mt */ lua_pushliteral(L, "CursesWindow"); lua_setfield(L, mt, "_type"); /* mt._type = "Curses Window" */ /* for k,v in pairs(t) do mt[k]=v end */ for (lua_pushnil(L); lua_next(L, t) != 0;) lua_setfield(L, mt, lua_tostring(L, -2)); lua_pop(L, 1); /* pop mt */ return 1; } ================================================ FILE: src/ldebug.c ================================================ /* ** $Id: ldebug.c,v 2.29.1.6 2008/05/08 16:56:26 roberto Exp $ ** Debug Interface ** See Copyright Notice in lua.h */ #include #include #include #define ldebug_c #define LUA_CORE #include "lua.h" #include "lapi.h" #include "lcode.h" #include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "lobject.h" #include "lopcodes.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "ltm.h" #include "lvm.h" static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name); static int currentpc (lua_State *L, CallInfo *ci) { if (!isLua(ci)) return -1; /* function is not a Lua function? */ if (ci == L->ci) ci->savedpc = L->savedpc; return pcRel(ci->savedpc, ci_func(ci)->l.p); } static int currentline (lua_State *L, CallInfo *ci) { int pc = currentpc(L, ci); if (pc < 0) return -1; /* only active lua functions have current-line information */ else return getline(ci_func(ci)->l.p, pc); } /* ** this function can be called asynchronous (e.g. during a signal) */ LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count) { if (func == NULL || mask == 0) { /* turn off hooks? */ mask = 0; func = NULL; } L->hook = func; L->basehookcount = count; resethookcount(L); L->hookmask = cast_byte(mask); return 1; } LUA_API lua_Hook lua_gethook (lua_State *L) { return L->hook; } LUA_API int lua_gethookmask (lua_State *L) { return L->hookmask; } LUA_API int lua_gethookcount (lua_State *L) { return L->basehookcount; } LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar) { int status; CallInfo *ci; lua_lock(L); for (ci = L->ci; level > 0 && ci > L->base_ci; ci--) { level--; if (f_isLua(ci)) /* Lua function? */ level -= ci->tailcalls; /* skip lost tail calls */ } if (level == 0 && ci > L->base_ci) { /* level found? */ status = 1; ar->i_ci = cast_int(ci - L->base_ci); } else if (level < 0) { /* level is of a lost tail call? */ status = 1; ar->i_ci = 0; } else status = 0; /* no such level */ lua_unlock(L); return status; } static Proto *getluaproto (CallInfo *ci) { return (isLua(ci) ? ci_func(ci)->l.p : NULL); } static const char *findlocal (lua_State *L, CallInfo *ci, int n) { const char *name; Proto *fp = getluaproto(ci); if (fp && (name = luaF_getlocalname(fp, n, currentpc(L, ci))) != NULL) return name; /* is a local variable in a Lua function */ else { StkId limit = (ci == L->ci) ? L->top : (ci+1)->func; if (limit - ci->base >= n && n > 0) /* is 'n' inside 'ci' stack? */ return "(*temporary)"; else return NULL; } } LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n) { CallInfo *ci = L->base_ci + ar->i_ci; const char *name = findlocal(L, ci, n); lua_lock(L); if (name) luaA_pushobject(L, ci->base + (n - 1)); lua_unlock(L); return name; } LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) { CallInfo *ci = L->base_ci + ar->i_ci; const char *name = findlocal(L, ci, n); lua_lock(L); if (name) setobjs2s(L, ci->base + (n - 1), L->top - 1); L->top--; /* pop value */ lua_unlock(L); return name; } static void funcinfo (lua_Debug *ar, Closure *cl) { if (cl->c.isC) { ar->source = "=[C]"; ar->linedefined = -1; ar->lastlinedefined = -1; ar->what = "C"; } else { ar->source = getstr(cl->l.p->source); ar->linedefined = cl->l.p->linedefined; ar->lastlinedefined = cl->l.p->lastlinedefined; ar->what = (ar->linedefined == 0) ? "main" : "Lua"; } luaO_chunkid(ar->short_src, ar->source, LUA_IDSIZE); } static void info_tailcall (lua_Debug *ar) { ar->name = ar->namewhat = ""; ar->what = "tail"; ar->lastlinedefined = ar->linedefined = ar->currentline = -1; ar->source = "=(tail call)"; luaO_chunkid(ar->short_src, ar->source, LUA_IDSIZE); ar->nups = 0; } static void collectvalidlines (lua_State *L, Closure *f) { if (f == NULL || f->c.isC) { setnilvalue(L->top); } else { Table *t = luaH_new(L, 0, 0); int *lineinfo = f->l.p->lineinfo; int i; for (i=0; il.p->sizelineinfo; i++) setbvalue(luaH_setnum(L, t, lineinfo[i]), 1); sethvalue(L, L->top, t); } incr_top(L); } static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar, Closure *f, CallInfo *ci) { int status = 1; if (f == NULL) { info_tailcall(ar); return status; } for (; *what; what++) { switch (*what) { case 'S': { funcinfo(ar, f); break; } case 'l': { ar->currentline = (ci) ? currentline(L, ci) : -1; break; } case 'u': { ar->nups = f->c.nupvalues; break; } case 'n': { ar->namewhat = (ci) ? getfuncname(L, ci, &ar->name) : NULL; if (ar->namewhat == NULL) { ar->namewhat = ""; /* not found */ ar->name = NULL; } break; } case 'L': case 'f': /* handled by lua_getinfo */ break; default: status = 0; /* invalid option */ } } return status; } LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) { int status; Closure *f = NULL; CallInfo *ci = NULL; lua_lock(L); if (*what == '>') { StkId func = L->top - 1; luai_apicheck(L, ttisfunction(func)); what++; /* skip the '>' */ f = clvalue(func); L->top--; /* pop function */ } else if (ar->i_ci != 0) { /* no tail call? */ ci = L->base_ci + ar->i_ci; lua_assert(ttisfunction(ci->func)); f = clvalue(ci->func); } status = auxgetinfo(L, what, ar, f, ci); if (strchr(what, 'f')) { if (f == NULL) setnilvalue(L->top); else setclvalue(L, L->top, f); incr_top(L); } if (strchr(what, 'L')) collectvalidlines(L, f); lua_unlock(L); return status; } /* ** {====================================================== ** Symbolic Execution and code checker ** ======================================================= */ #define check(x) if (!(x)) return 0; #define checkjump(pt,pc) check(0 <= pc && pc < pt->sizecode) #define checkreg(pt,reg) check((reg) < (pt)->maxstacksize) static int precheck (const Proto *pt) { check(pt->maxstacksize <= MAXSTACK); check(pt->numparams+(pt->is_vararg & VARARG_HASARG) <= pt->maxstacksize); check(!(pt->is_vararg & VARARG_NEEDSARG) || (pt->is_vararg & VARARG_HASARG)); check(pt->sizeupvalues <= pt->nups); check(pt->sizelineinfo == pt->sizecode || pt->sizelineinfo == 0); check(pt->sizecode > 0 && GET_OPCODE(pt->code[pt->sizecode-1]) == OP_RETURN); return 1; } #define checkopenop(pt,pc) luaG_checkopenop((pt)->code[(pc)+1]) int luaG_checkopenop (Instruction i) { switch (GET_OPCODE(i)) { case OP_CALL: case OP_TAILCALL: case OP_RETURN: case OP_SETLIST: { check(GETARG_B(i) == 0); return 1; } default: return 0; /* invalid instruction after an open call */ } } static int checkArgMode (const Proto *pt, int r, enum OpArgMask mode) { switch (mode) { case OpArgN: check(r == 0); break; case OpArgU: break; case OpArgR: checkreg(pt, r); break; case OpArgK: check(ISK(r) ? INDEXK(r) < pt->sizek : r < pt->maxstacksize); break; } return 1; } Instruction symbexec (const Proto *pt, int lastpc, int reg) { int pc; int last; /* stores position of last instruction that changed `reg' */ last = pt->sizecode-1; /* points to final return (a `neutral' instruction) */ check(precheck(pt)); for (pc = 0; pc < lastpc; pc++) { Instruction i = pt->code[pc]; OpCode op = GET_OPCODE(i); int a = GETARG_A(i); int b = 0; int c = 0; check(op < NUM_OPCODES); checkreg(pt, a); switch (getOpMode(op)) { case iABC: { b = GETARG_B(i); c = GETARG_C(i); check(checkArgMode(pt, b, getBMode(op))); check(checkArgMode(pt, c, getCMode(op))); break; } case iABx: { b = GETARG_Bx(i); if (getBMode(op) == OpArgK) check(b < pt->sizek); break; } case iAsBx: { b = GETARG_sBx(i); if (getBMode(op) == OpArgR) { int dest = pc+1+b; check(0 <= dest && dest < pt->sizecode); if (dest > 0) { int j; /* check that it does not jump to a setlist count; this is tricky, because the count from a previous setlist may have the same value of an invalid setlist; so, we must go all the way back to the first of them (if any) */ for (j = 0; j < dest; j++) { Instruction d = pt->code[dest-1-j]; if (!(GET_OPCODE(d) == OP_SETLIST && GETARG_C(d) == 0)) break; } /* if 'j' is even, previous value is not a setlist (even if it looks like one) */ check((j&1) == 0); } } break; } } if (testAMode(op)) { if (a == reg) last = pc; /* change register `a' */ } if (testTMode(op)) { check(pc+2 < pt->sizecode); /* check skip */ check(GET_OPCODE(pt->code[pc+1]) == OP_JMP); } switch (op) { case OP_LOADBOOL: { if (c == 1) { /* does it jump? */ check(pc+2 < pt->sizecode); /* check its jump */ check(GET_OPCODE(pt->code[pc+1]) != OP_SETLIST || GETARG_C(pt->code[pc+1]) != 0); } break; } case OP_LOADNIL: { if (a <= reg && reg <= b) last = pc; /* set registers from `a' to `b' */ break; } case OP_GETUPVAL: case OP_SETUPVAL: { check(b < pt->nups); break; } case OP_GETGLOBAL: case OP_SETGLOBAL: { check(ttisstring(&pt->k[b])); break; } case OP_SELF: { checkreg(pt, a+1); if (reg == a+1) last = pc; break; } case OP_CONCAT: { check(b < c); /* at least two operands */ break; } case OP_TFORLOOP: { check(c >= 1); /* at least one result (control variable) */ checkreg(pt, a+2+c); /* space for results */ if (reg >= a+2) last = pc; /* affect all regs above its base */ break; } case OP_FORLOOP: case OP_FORPREP: checkreg(pt, a+3); /* fall through */ case OP_JMP: { int dest = pc+1+b; /* not full check and jump is forward and do not skip `lastpc'? */ if (reg != NO_REG && pc < dest && dest <= lastpc) pc += b; /* do the jump */ break; } case OP_CALL: case OP_TAILCALL: { if (b != 0) { checkreg(pt, a+b-1); } c--; /* c = num. returns */ if (c == LUA_MULTRET) { check(checkopenop(pt, pc)); } else if (c != 0) checkreg(pt, a+c-1); if (reg >= a) last = pc; /* affect all registers above base */ break; } case OP_RETURN: { b--; /* b = num. returns */ if (b > 0) checkreg(pt, a+b-1); break; } case OP_SETLIST: { if (b > 0) checkreg(pt, a + b); if (c == 0) { pc++; check(pc < pt->sizecode - 1); } break; } case OP_CLOSURE: { int nup, j; check(b < pt->sizep); nup = pt->p[b]->nups; check(pc + nup < pt->sizecode); for (j = 1; j <= nup; j++) { OpCode op1 = GET_OPCODE(pt->code[pc + j]); check(op1 == OP_GETUPVAL || op1 == OP_MOVE); } if (reg != NO_REG) /* tracing? */ pc += nup; /* do not 'execute' these pseudo-instructions */ break; } case OP_VARARG: { check((pt->is_vararg & VARARG_ISVARARG) && !(pt->is_vararg & VARARG_NEEDSARG)); b--; if (b == LUA_MULTRET) check(checkopenop(pt, pc)); checkreg(pt, a+b-1); break; } default: break; } } return pt->code[last]; } #undef check #undef checkjump #undef checkreg /* }====================================================== */ int luaG_checkcode (const Proto *pt) { return (symbexec(pt, pt->sizecode, NO_REG) != 0); } static const char *kname (Proto *p, int c) { if (ISK(c) && ttisstring(&p->k[INDEXK(c)])) return svalue(&p->k[INDEXK(c)]); else return "?"; } static const char *getobjname (lua_State *L, CallInfo *ci, int stackpos, const char **name) { if (isLua(ci)) { /* a Lua function? */ Proto *p = ci_func(ci)->l.p; int pc = currentpc(L, ci); Instruction i; *name = luaF_getlocalname(p, stackpos+1, pc); if (*name) /* is a local? */ return "local"; i = symbexec(p, pc, stackpos); /* try symbolic execution */ lua_assert(pc != -1); switch (GET_OPCODE(i)) { case OP_GETGLOBAL: { int g = GETARG_Bx(i); /* global index */ lua_assert(ttisstring(&p->k[g])); *name = svalue(&p->k[g]); return "global"; } case OP_MOVE: { int a = GETARG_A(i); int b = GETARG_B(i); /* move from `b' to `a' */ if (b < a) return getobjname(L, ci, b, name); /* get name for `b' */ break; } case OP_GETTABLE: { int k = GETARG_C(i); /* key index */ *name = kname(p, k); return "field"; } case OP_GETUPVAL: { int u = GETARG_B(i); /* upvalue index */ *name = p->upvalues ? getstr(p->upvalues[u]) : "?"; return "upvalue"; } case OP_SELF: { int k = GETARG_C(i); /* key index */ *name = kname(p, k); return "method"; } default: break; } } return NULL; /* no useful name found */ } static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) { Instruction i; if ((isLua(ci) && ci->tailcalls > 0) || !isLua(ci - 1)) return NULL; /* calling function is not Lua (or is unknown) */ ci--; /* calling function */ i = ci_func(ci)->l.p->code[currentpc(L, ci)]; if (GET_OPCODE(i) == OP_CALL || GET_OPCODE(i) == OP_TAILCALL || GET_OPCODE(i) == OP_TFORLOOP) return getobjname(L, ci, GETARG_A(i), name); else return NULL; /* no useful name can be found */ } /* only ANSI way to check whether a pointer points to an array */ static int isinstack (CallInfo *ci, const TValue *o) { StkId p; for (p = ci->base; p < ci->top; p++) if (o == p) return 1; return 0; } void luaG_typeerror (lua_State *L, const TValue *o, const char *op) { const char *name = NULL; const char *t = luaT_typenames[ttype(o)]; const char *kind = (isinstack(L->ci, o)) ? getobjname(L, L->ci, cast_int(o - L->base), &name) : NULL; if (kind) luaG_runerror(L, "attempt to %s %s " LUA_QS " (a %s value)", op, kind, name, t); else luaG_runerror(L, "attempt to %s a %s value", op, t); } void luaG_concaterror (lua_State *L, StkId p1, StkId p2) { if (ttisstring(p1) || ttisnumber(p1)) p1 = p2; lua_assert(!ttisstring(p1) && !ttisnumber(p1)); luaG_typeerror(L, p1, "concatenate"); } void luaG_aritherror (lua_State *L, const TValue *p1, const TValue *p2) { TValue temp; if (luaV_tonumber(p1, &temp) == NULL) p2 = p1; /* first operand is wrong */ luaG_typeerror(L, p2, "perform arithmetic on"); } int luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2) { const char *t1 = luaT_typenames[ttype(p1)]; const char *t2 = luaT_typenames[ttype(p2)]; if (t1[2] == t2[2]) luaG_runerror(L, "attempt to compare two %s values", t1); else luaG_runerror(L, "attempt to compare %s with %s", t1, t2); return 0; } static void addinfo (lua_State *L, const char *msg) { CallInfo *ci = L->ci; if (isLua(ci)) { /* is Lua code? */ char buff[LUA_IDSIZE]; /* add file:line information */ int line = currentline(L, ci); luaO_chunkid(buff, getstr(getluaproto(ci)->source), LUA_IDSIZE); luaO_pushfstring(L, "%s:%d: %s", buff, line, msg); } } void luaG_errormsg (lua_State *L) { if (L->errfunc != 0) { /* is there an error handling function? */ StkId errfunc = restorestack(L, L->errfunc); if (!ttisfunction(errfunc)) luaD_throw(L, LUA_ERRERR); setobjs2s(L, L->top, L->top - 1); /* move argument */ setobjs2s(L, L->top - 1, errfunc); /* push function */ incr_top(L); luaD_call(L, L->top - 2, 1); /* call it */ } luaD_throw(L, LUA_ERRRUN); } void luaG_runerror (lua_State *L, const char *fmt, ...) { va_list argp; va_start(argp, fmt); addinfo(L, luaO_pushvfstring(L, fmt, argp)); va_end(argp); luaG_errormsg(L); } ================================================ FILE: src/ldebug.h ================================================ /* ** $Id: ldebug.h,v 2.3.1.1 2007/12/27 13:02:25 roberto Exp $ ** Auxiliary functions from Debug Interface module ** See Copyright Notice in lua.h */ #ifndef ldebug_h #define ldebug_h #include "lstate.h" #define pcRel(pc, p) (cast(int, (pc) - (p)->code) - 1) #define getline(f,pc) (((f)->lineinfo) ? (f)->lineinfo[pc] : 0) #define resethookcount(L) (L->hookcount = L->basehookcount) LUAI_FUNC void luaG_typeerror (lua_State *L, const TValue *o, const char *opname); LUAI_FUNC void luaG_concaterror (lua_State *L, StkId p1, StkId p2); LUAI_FUNC void luaG_aritherror (lua_State *L, const TValue *p1, const TValue *p2); LUAI_FUNC int luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2); LUAI_FUNC void luaG_runerror (lua_State *L, const char *fmt, ...); LUAI_FUNC void luaG_errormsg (lua_State *L); LUAI_FUNC int luaG_checkcode (const Proto *pt); LUAI_FUNC int luaG_checkopenop (Instruction i); #endif ================================================ FILE: src/ldo.c ================================================ /* ** $Id: ldo.c,v 2.38.1.4 2012/01/18 02:27:10 roberto Exp $ ** Stack and Call structure of Lua ** See Copyright Notice in lua.h */ #include #include #include #define ldo_c #define LUA_CORE #include "lua.h" #include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "lgc.h" #include "lmem.h" #include "lobject.h" #include "lopcodes.h" #include "lparser.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "ltm.h" #include "lundump.h" #include "lvm.h" #include "lzio.h" /* ** {====================================================== ** Error-recovery functions ** ======================================================= */ /* chain list of long jump buffers */ struct lua_longjmp { struct lua_longjmp *previous; luai_jmpbuf b; volatile int status; /* error code */ }; void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) { switch (errcode) { case LUA_ERRMEM: { setsvalue2s(L, oldtop, luaS_newliteral(L, MEMERRMSG)); break; } case LUA_ERRERR: { setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling")); break; } case LUA_ERRSYNTAX: case LUA_ERRRUN: { setobjs2s(L, oldtop, L->top - 1); /* error message on current top */ break; } } L->top = oldtop + 1; } static void restore_stack_limit (lua_State *L) { lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1); if (L->size_ci > LUAI_MAXCALLS) { /* there was an overflow? */ int inuse = cast_int(L->ci - L->base_ci); if (inuse + 1 < LUAI_MAXCALLS) /* can `undo' overflow? */ luaD_reallocCI(L, LUAI_MAXCALLS); } } static void resetstack (lua_State *L, int status) { L->ci = L->base_ci; L->base = L->ci->base; luaF_close(L, L->base); /* close eventual pending closures */ luaD_seterrorobj(L, status, L->base); L->nCcalls = L->baseCcalls; L->allowhook = 1; restore_stack_limit(L); L->errfunc = 0; L->errorJmp = NULL; } void luaD_throw (lua_State *L, int errcode) { if (L->errorJmp) { L->errorJmp->status = errcode; LUAI_THROW(L, L->errorJmp); } else { L->status = cast_byte(errcode); if (G(L)->panic) { resetstack(L, errcode); lua_unlock(L); G(L)->panic(L); } exit(EXIT_FAILURE); } } int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { struct lua_longjmp lj; lj.status = 0; lj.previous = L->errorJmp; /* chain new error handler */ L->errorJmp = &lj; LUAI_TRY(L, &lj, (*f)(L, ud); ); L->errorJmp = lj.previous; /* restore old error handler */ return lj.status; } /* }====================================================== */ static void correctstack (lua_State *L, TValue *oldstack) { CallInfo *ci; GCObject *up; L->top = (L->top - oldstack) + L->stack; for (up = L->openupval; up != NULL; up = up->gch.next) gco2uv(up)->v = (gco2uv(up)->v - oldstack) + L->stack; for (ci = L->base_ci; ci <= L->ci; ci++) { ci->top = (ci->top - oldstack) + L->stack; ci->base = (ci->base - oldstack) + L->stack; ci->func = (ci->func - oldstack) + L->stack; } L->base = (L->base - oldstack) + L->stack; } void luaD_reallocstack (lua_State *L, int newsize) { TValue *oldstack = L->stack; int realsize = newsize + 1 + EXTRA_STACK; lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1); luaM_reallocvector(L, L->stack, L->stacksize, realsize, TValue); L->stacksize = realsize; L->stack_last = L->stack+newsize; correctstack(L, oldstack); } void luaD_reallocCI (lua_State *L, int newsize) { CallInfo *oldci = L->base_ci; luaM_reallocvector(L, L->base_ci, L->size_ci, newsize, CallInfo); L->size_ci = newsize; L->ci = (L->ci - oldci) + L->base_ci; L->end_ci = L->base_ci + L->size_ci - 1; } void luaD_growstack (lua_State *L, int n) { if (n <= L->stacksize) /* double size is enough? */ luaD_reallocstack(L, 2*L->stacksize); else luaD_reallocstack(L, L->stacksize + n); } static CallInfo *growCI (lua_State *L) { if (L->size_ci > LUAI_MAXCALLS) /* overflow while handling overflow? */ luaD_throw(L, LUA_ERRERR); else { luaD_reallocCI(L, 2*L->size_ci); if (L->size_ci > LUAI_MAXCALLS) luaG_runerror(L, "stack overflow"); } return ++L->ci; } void luaD_callhook (lua_State *L, int event, int line) { lua_Hook hook = L->hook; if (hook && L->allowhook) { ptrdiff_t top = savestack(L, L->top); ptrdiff_t ci_top = savestack(L, L->ci->top); lua_Debug ar; ar.event = event; ar.currentline = line; if (event == LUA_HOOKTAILRET) ar.i_ci = 0; /* tail call; no debug information about it */ else ar.i_ci = cast_int(L->ci - L->base_ci); luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ L->ci->top = L->top + LUA_MINSTACK; lua_assert(L->ci->top <= L->stack_last); L->allowhook = 0; /* cannot call hooks inside a hook */ lua_unlock(L); (*hook)(L, &ar); lua_lock(L); lua_assert(!L->allowhook); L->allowhook = 1; L->ci->top = restorestack(L, ci_top); L->top = restorestack(L, top); } } static StkId adjust_varargs (lua_State *L, Proto *p, int actual) { int i; int nfixargs = p->numparams; Table *htab = NULL; StkId base, fixed; for (; actual < nfixargs; ++actual) setnilvalue(L->top++); #if defined(LUA_COMPAT_VARARG) if (p->is_vararg & VARARG_NEEDSARG) { /* compat. with old-style vararg? */ int nvar = actual - nfixargs; /* number of extra arguments */ lua_assert(p->is_vararg & VARARG_HASARG); luaC_checkGC(L); luaD_checkstack(L, p->maxstacksize); htab = luaH_new(L, nvar, 1); /* create `arg' table */ for (i=0; itop - nvar + i); /* store counter in field `n' */ setnvalue(luaH_setstr(L, htab, luaS_newliteral(L, "n")), cast_num(nvar)); } #endif /* move fixed parameters to final position */ fixed = L->top - actual; /* first fixed argument */ base = L->top; /* final position of first argument */ for (i=0; itop++, fixed+i); setnilvalue(fixed+i); } /* add `arg' parameter */ if (htab) { sethvalue(L, L->top++, htab); lua_assert(iswhite(obj2gco(htab))); } return base; } static StkId tryfuncTM (lua_State *L, StkId func) { const TValue *tm = luaT_gettmbyobj(L, func, TM_CALL); StkId p; ptrdiff_t funcr = savestack(L, func); if (!ttisfunction(tm)) luaG_typeerror(L, func, "call"); /* Open a hole inside the stack at `func' */ for (p = L->top; p > func; p--) setobjs2s(L, p, p-1); incr_top(L); func = restorestack(L, funcr); /* previous call may change stack */ setobj2s(L, func, tm); /* tag method is the new function to be called */ return func; } extern void record_metadata_about_function_call (lua_State *L, CallInfo *ci); #define inc_ci(L) \ ((L->ci == L->end_ci) ? growCI(L) : \ (condhardstacktests(luaD_reallocCI(L, L->size_ci)), ++L->ci)) int luaD_precall (lua_State *L, StkId func, int nresults) { LClosure *cl; ptrdiff_t funcr; if (!ttisfunction(func)) /* `func' is not a function? */ func = tryfuncTM(L, func); /* check the `function' tag method */ funcr = savestack(L, func); cl = &clvalue(func)->l; L->ci->savedpc = L->savedpc; if (!cl->isC) { /* Lua function? prepare its call */ CallInfo *ci; StkId st, base; Proto *p = cl->p; luaD_checkstack(L, p->maxstacksize); func = restorestack(L, funcr); if (!p->is_vararg) { /* no varargs? */ base = func + 1; if (L->top > base + p->numparams) L->top = base + p->numparams; } else { /* vararg function */ int nargs = cast_int(L->top - func) - 1; base = adjust_varargs(L, p, nargs); func = restorestack(L, funcr); /* previous call may change the stack */ } ci = inc_ci(L); /* now `enter' new function */ ci->func = func; L->base = ci->base = base; ci->top = L->base + p->maxstacksize; lua_assert(ci->top <= L->stack_last); L->savedpc = p->code; /* starting point */ ci->tailcalls = 0; ci->nresults = nresults; for (st = L->top; st < ci->top; st++) setnilvalue(st); L->top = ci->top; record_metadata_about_function_call(L, ci); if (L->hookmask & LUA_MASKCALL) { L->savedpc++; /* hooks assume 'pc' is already incremented */ luaD_callhook(L, LUA_HOOKCALL, -1); L->savedpc--; /* correct 'pc' */ } return PCRLUA; } else { /* if is a C function, call it */ CallInfo *ci; int n; luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ ci = inc_ci(L); /* now `enter' new function */ ci->func = restorestack(L, funcr); L->base = ci->base = ci->func + 1; ci->top = L->top + LUA_MINSTACK; lua_assert(ci->top <= L->stack_last); ci->nresults = nresults; if (L->hookmask & LUA_MASKCALL) luaD_callhook(L, LUA_HOOKCALL, -1); lua_unlock(L); n = (*curr_func(L)->c.f)(L); /* do the actual call */ lua_lock(L); if (n < 0) /* yielding? */ return PCRYIELD; else { luaD_poscall(L, L->top - n); return PCRC; } } } static StkId callrethooks (lua_State *L, StkId firstResult) { ptrdiff_t fr = savestack(L, firstResult); /* next call may change stack */ luaD_callhook(L, LUA_HOOKRET, -1); if (f_isLua(L->ci)) { /* Lua function? */ while ((L->hookmask & LUA_MASKRET) && L->ci->tailcalls--) /* tail calls */ luaD_callhook(L, LUA_HOOKTAILRET, -1); } return restorestack(L, fr); } int luaD_poscall (lua_State *L, StkId firstResult) { StkId res; int wanted, i; CallInfo *ci; if (L->hookmask & LUA_MASKRET) firstResult = callrethooks(L, firstResult); ci = L->ci--; res = ci->func; /* res == final position of 1st result */ wanted = ci->nresults; L->base = (ci - 1)->base; /* restore base */ L->savedpc = (ci - 1)->savedpc; /* restore savedpc */ /* move results to correct place */ for (i = wanted; i != 0 && firstResult < L->top; i--) setobjs2s(L, res++, firstResult++); while (i-- > 0) setnilvalue(res++); L->top = res; return (wanted - LUA_MULTRET); /* 0 iff wanted == LUA_MULTRET */ } /* ** Call a function (C or Lua). The function to be called is at *func. ** The arguments are on the stack, right after the function. ** When returns, all the results are on the stack, starting at the original ** function position. */ void luaD_call (lua_State *L, StkId func, int nResults) { if (++L->nCcalls >= LUAI_MAXCCALLS) { if (L->nCcalls == LUAI_MAXCCALLS) luaG_runerror(L, "C stack overflow"); else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) luaD_throw(L, LUA_ERRERR); /* error while handing stack error */ } if (luaD_precall(L, func, nResults) == PCRLUA) /* is a Lua function? */ luaV_execute(L, 1); /* call it */ L->nCcalls--; luaC_checkGC(L); } static void resume (lua_State *L, void *ud) { StkId firstArg = cast(StkId, ud); CallInfo *ci = L->ci; if (L->status == 0) { /* start coroutine? */ lua_assert(ci == L->base_ci && firstArg > L->base); if (luaD_precall(L, firstArg - 1, LUA_MULTRET) != PCRLUA) return; } else { /* resuming from previous yield */ lua_assert(L->status == LUA_YIELD); L->status = 0; if (!f_isLua(ci)) { /* `common' yield? */ /* finish interrupted execution of `OP_CALL' */ lua_assert(GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_CALL || GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_TAILCALL); if (luaD_poscall(L, firstArg)) /* complete it... */ L->top = L->ci->top; /* and correct top if not multiple results */ } else /* yielded inside a hook: just continue its execution */ L->base = L->ci->base; } luaV_execute(L, cast_int(L->ci - L->base_ci)); } static int resume_error (lua_State *L, const char *msg) { L->top = L->ci->base; setsvalue2s(L, L->top, luaS_new(L, msg)); incr_top(L); lua_unlock(L); return LUA_ERRRUN; } LUA_API int lua_resume (lua_State *L, int nargs) { int status; lua_lock(L); if (L->status != LUA_YIELD && (L->status != 0 || L->ci != L->base_ci)) return resume_error(L, "cannot resume non-suspended coroutine"); if (L->nCcalls >= LUAI_MAXCCALLS) return resume_error(L, "C stack overflow"); lua_assert(L->errfunc == 0); L->baseCcalls = ++L->nCcalls; status = luaD_rawrunprotected(L, resume, L->top - nargs); if (status != 0) { /* error? */ L->status = cast_byte(status); /* mark thread as `dead' */ luaD_seterrorobj(L, status, L->top); L->ci->top = L->top; } else { lua_assert(L->nCcalls == L->baseCcalls); status = L->status; } --L->nCcalls; lua_unlock(L); return status; } LUA_API int lua_yield (lua_State *L, int nresults) { lua_lock(L); if (L->nCcalls > L->baseCcalls) luaG_runerror(L, "attempt to yield across metamethod/C-call boundary"); L->base = L->top - nresults; /* protect stack slots below */ L->status = LUA_YIELD; lua_unlock(L); return -1; } int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t old_top, ptrdiff_t ef) { int status; unsigned short oldnCcalls = L->nCcalls; ptrdiff_t old_ci = saveci(L, L->ci); lu_byte old_allowhooks = L->allowhook; ptrdiff_t old_errfunc = L->errfunc; L->errfunc = ef; status = luaD_rawrunprotected(L, func, u); if (status != 0) { /* an error occurred? */ StkId oldtop = restorestack(L, old_top); luaF_close(L, oldtop); /* close eventual pending closures */ luaD_seterrorobj(L, status, oldtop); L->nCcalls = oldnCcalls; L->ci = restoreci(L, old_ci); L->base = L->ci->base; L->savedpc = L->ci->savedpc; L->allowhook = old_allowhooks; restore_stack_limit(L); } L->errfunc = old_errfunc; return status; } /* ** Execute a protected parser. */ struct SParser { /* data to `f_parser' */ ZIO *z; Mbuffer buff; /* buffer to be used by the scanner */ const char *name; }; static void f_parser (lua_State *L, void *ud) { int i; Proto *tf; Closure *cl; struct SParser *p = cast(struct SParser *, ud); int c = luaZ_lookahead(p->z); luaC_checkGC(L); tf = ((c == LUA_SIGNATURE[0]) ? luaU_undump : luaY_parser)(L, p->z, &p->buff, p->name); cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L))); cl->l.p = tf; for (i = 0; i < tf->nups; i++) /* initialize eventual upvalues */ cl->l.upvals[i] = luaF_newupval(L); setclvalue(L, L->top, cl); incr_top(L); } int luaD_protectedparser (lua_State *L, ZIO *z, const char *name) { struct SParser p; int status; p.z = z; p.name = name; luaZ_initbuffer(L, &p.buff); status = luaD_pcall(L, f_parser, &p, savestack(L, L->top), L->errfunc); luaZ_freebuffer(L, &p.buff); return status; } ================================================ FILE: src/ldo.h ================================================ /* ** $Id: ldo.h,v 2.7.1.1 2007/12/27 13:02:25 roberto Exp $ ** Stack and Call structure of Lua ** See Copyright Notice in lua.h */ #ifndef ldo_h #define ldo_h #include "lobject.h" #include "lstate.h" #include "lzio.h" #define luaD_checkstack(L,n) \ if ((char *)L->stack_last - (char *)L->top <= (n)*(int)sizeof(TValue)) \ luaD_growstack(L, n); \ else condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1)); #define incr_top(L) {luaD_checkstack(L,1); L->top++;} #define savestack(L,p) ((char *)(p) - (char *)L->stack) #define restorestack(L,n) ((TValue *)((char *)L->stack + (n))) #define saveci(L,p) ((char *)(p) - (char *)L->base_ci) #define restoreci(L,n) ((CallInfo *)((char *)L->base_ci + (n))) /* results from luaD_precall */ #define PCRLUA 0 /* initiated a call to a Lua function */ #define PCRC 1 /* did a call to a C function */ #define PCRYIELD 2 /* C funtion yielded */ /* type of protected functions, to be ran by `runprotected' */ typedef void (*Pfunc) (lua_State *L, void *ud); LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name); LUAI_FUNC void luaD_callhook (lua_State *L, int event, int line); LUAI_FUNC int luaD_precall (lua_State *L, StkId func, int nresults); LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults); LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t oldtop, ptrdiff_t ef); LUAI_FUNC int luaD_poscall (lua_State *L, StkId firstResult); LUAI_FUNC void luaD_reallocCI (lua_State *L, int newsize); LUAI_FUNC void luaD_reallocstack (lua_State *L, int newsize); LUAI_FUNC void luaD_growstack (lua_State *L, int n); LUAI_FUNC void luaD_throw (lua_State *L, int errcode); LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); LUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop); #endif ================================================ FILE: src/ldump.c ================================================ /* ** $Id: ldump.c,v 2.8.1.1 2007/12/27 13:02:25 roberto Exp $ ** save precompiled Lua chunks ** See Copyright Notice in lua.h */ #include #define ldump_c #define LUA_CORE #include "lua.h" #include "lobject.h" #include "lstate.h" #include "lundump.h" typedef struct { lua_State* L; lua_Writer writer; void* data; int strip; int status; } DumpState; #define DumpMem(b,n,size,D) DumpBlock(b,(n)*(size),D) #define DumpVar(x,D) DumpMem(&x,1,sizeof(x),D) static void DumpBlock(const void* b, size_t size, DumpState* D) { if (D->status==0) { lua_unlock(D->L); D->status=(*D->writer)(D->L,b,size,D->data); lua_lock(D->L); } } static void DumpChar(int y, DumpState* D) { char x=(char)y; DumpVar(x,D); } static void DumpInt(int x, DumpState* D) { DumpVar(x,D); } static void DumpNumber(lua_Number x, DumpState* D) { DumpVar(x,D); } static void DumpVector(const void* b, int n, size_t size, DumpState* D) { DumpInt(n,D); DumpMem(b,n,size,D); } static void DumpString(const TString* s, DumpState* D) { if (s==NULL || getstr(s)==NULL) { size_t size=0; DumpVar(size,D); } else { size_t size=s->tsv.len+1; /* include trailing '\0' */ DumpVar(size,D); DumpBlock(getstr(s),size,D); } } #define DumpCode(f,D) DumpVector(f->code,f->sizecode,sizeof(Instruction),D) static void DumpFunction(const Proto* f, const TString* p, DumpState* D); static void DumpConstants(const Proto* f, DumpState* D) { int i,n=f->sizek; DumpInt(n,D); for (i=0; ik[i]; DumpChar(ttype(o),D); switch (ttype(o)) { case LUA_TNIL: break; case LUA_TBOOLEAN: DumpChar(bvalue(o),D); break; case LUA_TNUMBER: DumpNumber(nvalue(o),D); break; case LUA_TSTRING: DumpString(rawtsvalue(o),D); break; default: lua_assert(0); /* cannot happen */ break; } } n=f->sizep; DumpInt(n,D); for (i=0; ip[i],f->source,D); } static void DumpDebug(const Proto* f, DumpState* D) { int i,n; n= (D->strip) ? 0 : f->sizelineinfo; DumpVector(f->lineinfo,n,sizeof(int),D); n= (D->strip) ? 0 : f->sizelocvars; DumpInt(n,D); for (i=0; ilocvars[i].varname,D); DumpInt(f->locvars[i].startpc,D); DumpInt(f->locvars[i].endpc,D); } n= (D->strip) ? 0 : f->sizeupvalues; DumpInt(n,D); for (i=0; iupvalues[i],D); } static void DumpFunction(const Proto* f, const TString* p, DumpState* D) { DumpString((f->source==p || D->strip) ? NULL : f->source,D); DumpInt(f->linedefined,D); DumpInt(f->lastlinedefined,D); DumpChar(f->nups,D); DumpChar(f->numparams,D); DumpChar(f->is_vararg,D); DumpChar(f->maxstacksize,D); DumpCode(f,D); DumpConstants(f,D); DumpDebug(f,D); } static void DumpHeader(DumpState* D) { char h[LUAC_HEADERSIZE]; luaU_header(h); DumpBlock(h,LUAC_HEADERSIZE,D); } /* ** dump Lua function as precompiled chunk */ int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, void* data, int strip) { DumpState D; D.L=L; D.writer=w; D.data=data; D.strip=strip; D.status=0; DumpHeader(&D); DumpFunction(f,NULL,&D); return D.status; } ================================================ FILE: src/lfunc.c ================================================ /* ** $Id: lfunc.c,v 2.12.1.2 2007/12/28 14:58:43 roberto Exp $ ** Auxiliary functions to manipulate prototypes and closures ** See Copyright Notice in lua.h */ #include #define lfunc_c #define LUA_CORE #include "lua.h" #include "lfunc.h" #include "lgc.h" #include "lmem.h" #include "lobject.h" #include "lstate.h" Closure *luaF_newCclosure (lua_State *L, int nelems, Table *e) { Closure *c = cast(Closure *, luaM_malloc(L, sizeCclosure(nelems))); luaC_link(L, obj2gco(c), LUA_TFUNCTION); c->c.isC = 1; c->c.env = e; c->c.nupvalues = cast_byte(nelems); return c; } Closure *luaF_newLclosure (lua_State *L, int nelems, Table *e) { Closure *c = cast(Closure *, luaM_malloc(L, sizeLclosure(nelems))); luaC_link(L, obj2gco(c), LUA_TFUNCTION); c->l.isC = 0; c->l.env = e; c->l.nupvalues = cast_byte(nelems); while (nelems--) c->l.upvals[nelems] = NULL; return c; } UpVal *luaF_newupval (lua_State *L) { UpVal *uv = luaM_new(L, UpVal); luaC_link(L, obj2gco(uv), LUA_TUPVAL); uv->v = &uv->u.value; setnilvalue(uv->v); return uv; } UpVal *luaF_findupval (lua_State *L, StkId level) { global_State *g = G(L); GCObject **pp = &L->openupval; UpVal *p; UpVal *uv; while (*pp != NULL && (p = ngcotouv(*pp))->v >= level) { lua_assert(p->v != &p->u.value); if (p->v == level) { /* found a corresponding upvalue? */ if (isdead(g, obj2gco(p))) /* is it dead? */ changewhite(obj2gco(p)); /* ressurect it */ return p; } pp = &p->next; } uv = luaM_new(L, UpVal); /* not found: create a new one */ uv->tt = LUA_TUPVAL; uv->marked = luaC_white(g); uv->v = level; /* current value lives in the stack */ uv->next = *pp; /* chain it in the proper position */ *pp = obj2gco(uv); uv->u.l.prev = &g->uvhead; /* double link it in `uvhead' list */ uv->u.l.next = g->uvhead.u.l.next; uv->u.l.next->u.l.prev = uv; g->uvhead.u.l.next = uv; lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); return uv; } static void unlinkupval (UpVal *uv) { lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); uv->u.l.next->u.l.prev = uv->u.l.prev; /* remove from `uvhead' list */ uv->u.l.prev->u.l.next = uv->u.l.next; } void luaF_freeupval (lua_State *L, UpVal *uv) { if (uv->v != &uv->u.value) /* is it open? */ unlinkupval(uv); /* remove from open list */ luaM_free(L, uv); /* free upvalue */ } void luaF_close (lua_State *L, StkId level) { UpVal *uv; global_State *g = G(L); while (L->openupval != NULL && (uv = ngcotouv(L->openupval))->v >= level) { GCObject *o = obj2gco(uv); lua_assert(!isblack(o) && uv->v != &uv->u.value); L->openupval = uv->next; /* remove from `open' list */ if (isdead(g, o)) luaF_freeupval(L, uv); /* free upvalue */ else { unlinkupval(uv); setobj(L, &uv->u.value, uv->v); uv->v = &uv->u.value; /* now current value lives here */ luaC_linkupval(L, uv); /* link upvalue into `gcroot' list */ } } } Proto *luaF_newproto (lua_State *L) { Proto *f = luaM_new(L, Proto); luaC_link(L, obj2gco(f), LUA_TPROTO); f->k = NULL; f->sizek = 0; f->p = NULL; f->sizep = 0; f->code = NULL; f->sizecode = 0; f->sizelineinfo = 0; f->sizeupvalues = 0; f->nups = 0; f->upvalues = NULL; f->numparams = 0; f->is_vararg = 0; f->maxstacksize = 0; f->lineinfo = NULL; f->sizelocvars = 0; f->locvars = NULL; f->linedefined = 0; f->lastlinedefined = 0; f->source = NULL; return f; } void luaF_freeproto (lua_State *L, Proto *f) { luaM_freearray(L, f->code, f->sizecode, Instruction); luaM_freearray(L, f->p, f->sizep, Proto *); luaM_freearray(L, f->k, f->sizek, TValue); luaM_freearray(L, f->lineinfo, f->sizelineinfo, int); luaM_freearray(L, f->locvars, f->sizelocvars, struct LocVar); luaM_freearray(L, f->upvalues, f->sizeupvalues, TString *); luaM_free(L, f); } void luaF_freeclosure (lua_State *L, Closure *c) { int size = (c->c.isC) ? sizeCclosure(c->c.nupvalues) : sizeLclosure(c->l.nupvalues); luaM_freemem(L, c, size); } /* ** Look for n-th local variable at line `line' in function `func'. ** Returns NULL if not found. */ const char *luaF_getlocalname (const Proto *f, int local_number, int pc) { int i; for (i = 0; isizelocvars && f->locvars[i].startpc <= pc; i++) { if (pc < f->locvars[i].endpc) { /* is variable active? */ local_number--; if (local_number == 0) return getstr(f->locvars[i].varname); } } return NULL; /* not found */ } ================================================ FILE: src/lfunc.h ================================================ /* ** $Id: lfunc.h,v 2.4.1.1 2007/12/27 13:02:25 roberto Exp $ ** Auxiliary functions to manipulate prototypes and closures ** See Copyright Notice in lua.h */ #ifndef lfunc_h #define lfunc_h #include "lobject.h" #define sizeCclosure(n) (cast(int, sizeof(CClosure)) + \ cast(int, sizeof(TValue)*((n)-1))) #define sizeLclosure(n) (cast(int, sizeof(LClosure)) + \ cast(int, sizeof(TValue *)*((n)-1))) LUAI_FUNC Proto *luaF_newproto (lua_State *L); LUAI_FUNC Closure *luaF_newCclosure (lua_State *L, int nelems, Table *e); LUAI_FUNC Closure *luaF_newLclosure (lua_State *L, int nelems, Table *e); LUAI_FUNC UpVal *luaF_newupval (lua_State *L); LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); LUAI_FUNC void luaF_close (lua_State *L, StkId level); LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f); LUAI_FUNC void luaF_freeclosure (lua_State *L, Closure *c); LUAI_FUNC void luaF_freeupval (lua_State *L, UpVal *uv); LUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number, int pc); #endif ================================================ FILE: src/lgc.c ================================================ /* ** $Id: lgc.c,v 2.38.1.2 2011/03/18 18:05:38 roberto Exp $ ** Garbage Collector ** See Copyright Notice in lua.h */ #include #define lgc_c #define LUA_CORE #include "lua.h" #include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "lgc.h" #include "lmem.h" #include "lobject.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "ltm.h" #define GCSTEPSIZE 1024u #define GCSWEEPMAX 40 #define GCSWEEPCOST 10 #define GCFINALIZECOST 100 #define maskmarks cast_byte(~(bitmask(BLACKBIT)|WHITEBITS)) #define makewhite(g,x) \ ((x)->gch.marked = cast_byte(((x)->gch.marked & maskmarks) | luaC_white(g))) #define white2gray(x) reset2bits((x)->gch.marked, WHITE0BIT, WHITE1BIT) #define black2gray(x) resetbit((x)->gch.marked, BLACKBIT) #define stringmark(s) reset2bits((s)->tsv.marked, WHITE0BIT, WHITE1BIT) #define isfinalized(u) testbit((u)->marked, FINALIZEDBIT) #define markfinalized(u) l_setbit((u)->marked, FINALIZEDBIT) #define KEYWEAK bitmask(KEYWEAKBIT) #define VALUEWEAK bitmask(VALUEWEAKBIT) #define markvalue(g,o) { checkconsistency(o); \ if (iscollectable(o) && iswhite(gcvalue(o))) reallymarkobject(g,gcvalue(o)); } #define markobject(g,t) { if (iswhite(obj2gco(t))) \ reallymarkobject(g, obj2gco(t)); } #define setthreshold(g) (g->GCthreshold = (g->estimate/100) * g->gcpause) static void removeentry (Node *n) { lua_assert(ttisnil(gval(n))); if (iscollectable(gkey(n))) setttype(gkey(n), LUA_TDEADKEY); /* dead key; remove it */ } static void reallymarkobject (global_State *g, GCObject *o) { lua_assert(iswhite(o) && !isdead(g, o)); white2gray(o); switch (o->gch.tt) { case LUA_TSTRING: { return; } case LUA_TUSERDATA: { Table *mt = gco2u(o)->metatable; gray2black(o); /* udata are never gray */ if (mt) markobject(g, mt); markobject(g, gco2u(o)->env); return; } case LUA_TUPVAL: { UpVal *uv = gco2uv(o); markvalue(g, uv->v); if (uv->v == &uv->u.value) /* closed? */ gray2black(o); /* open upvalues are never black */ return; } case LUA_TFUNCTION: { gco2cl(o)->c.gclist = g->gray; g->gray = o; break; } case LUA_TTABLE: { gco2h(o)->gclist = g->gray; g->gray = o; break; } case LUA_TTHREAD: { gco2th(o)->gclist = g->gray; g->gray = o; break; } case LUA_TPROTO: { gco2p(o)->gclist = g->gray; g->gray = o; break; } default: lua_assert(0); } } static void marktmu (global_State *g) { GCObject *u = g->tmudata; if (u) { do { u = u->gch.next; makewhite(g, u); /* may be marked, if left from previous GC */ reallymarkobject(g, u); } while (u != g->tmudata); } } /* move `dead' udata that need finalization to list `tmudata' */ size_t luaC_separateudata (lua_State *L, int all) { global_State *g = G(L); size_t deadmem = 0; GCObject **p = &g->mainthread->next; GCObject *curr; while ((curr = *p) != NULL) { if (!(iswhite(curr) || all) || isfinalized(gco2u(curr))) p = &curr->gch.next; /* don't bother with them */ else if (fasttm(L, gco2u(curr)->metatable, TM_GC) == NULL) { markfinalized(gco2u(curr)); /* don't need finalization */ p = &curr->gch.next; } else { /* must call its gc method */ deadmem += sizeudata(gco2u(curr)); markfinalized(gco2u(curr)); *p = curr->gch.next; /* link `curr' at the end of `tmudata' list */ if (g->tmudata == NULL) /* list is empty? */ g->tmudata = curr->gch.next = curr; /* creates a circular list */ else { curr->gch.next = g->tmudata->gch.next; g->tmudata->gch.next = curr; g->tmudata = curr; } } } return deadmem; } static int traversetable (global_State *g, Table *h) { int i; int weakkey = 0; int weakvalue = 0; const TValue *mode; if (h->metatable) markobject(g, h->metatable); mode = gfasttm(g, h->metatable, TM_MODE); if (mode && ttisstring(mode)) { /* is there a weak mode? */ weakkey = (strchr(svalue(mode), 'k') != NULL); weakvalue = (strchr(svalue(mode), 'v') != NULL); if (weakkey || weakvalue) { /* is really weak? */ h->marked &= ~(KEYWEAK | VALUEWEAK); /* clear bits */ h->marked |= cast_byte((weakkey << KEYWEAKBIT) | (weakvalue << VALUEWEAKBIT)); h->gclist = g->weak; /* must be cleared after GC, ... */ g->weak = obj2gco(h); /* ... so put in the appropriate list */ } } if (weakkey && weakvalue) return 1; if (!weakvalue) { i = h->sizearray; while (i--) markvalue(g, &h->array[i]); } i = sizenode(h); while (i--) { Node *n = gnode(h, i); lua_assert(ttype(gkey(n)) != LUA_TDEADKEY || ttisnil(gval(n))); if (ttisnil(gval(n))) removeentry(n); /* remove empty entries */ else { lua_assert(!ttisnil(gkey(n))); if (!weakkey) markvalue(g, gkey(n)); if (!weakvalue) markvalue(g, gval(n)); } } return weakkey || weakvalue; } /* ** All marks are conditional because a GC may happen while the ** prototype is still being created */ static void traverseproto (global_State *g, Proto *f) { int i; if (f->source) stringmark(f->source); for (i=0; isizek; i++) /* mark literals */ markvalue(g, &f->k[i]); for (i=0; isizeupvalues; i++) { /* mark upvalue names */ if (f->upvalues[i]) stringmark(f->upvalues[i]); } for (i=0; isizep; i++) { /* mark nested protos */ if (f->p[i]) markobject(g, f->p[i]); } for (i=0; isizelocvars; i++) { /* mark local-variable names */ if (f->locvars[i].varname) stringmark(f->locvars[i].varname); } } static void traverseclosure (global_State *g, Closure *cl) { markobject(g, cl->c.env); if (cl->c.isC) { int i; for (i=0; ic.nupvalues; i++) /* mark its upvalues */ markvalue(g, &cl->c.upvalue[i]); } else { int i; lua_assert(cl->l.nupvalues == cl->l.p->nups); markobject(g, cl->l.p); for (i=0; il.nupvalues; i++) /* mark its upvalues */ markobject(g, cl->l.upvals[i]); } } static void checkstacksizes (lua_State *L, StkId max) { int ci_used = cast_int(L->ci - L->base_ci); /* number of `ci' in use */ int s_used = cast_int(max - L->stack); /* part of stack in use */ if (L->size_ci > LUAI_MAXCALLS) /* handling overflow? */ return; /* do not touch the stacks */ if (4*ci_used < L->size_ci && 2*BASIC_CI_SIZE < L->size_ci) luaD_reallocCI(L, L->size_ci/2); /* still big enough... */ condhardstacktests(luaD_reallocCI(L, ci_used + 1)); if (4*s_used < L->stacksize && 2*(BASIC_STACK_SIZE+EXTRA_STACK) < L->stacksize) luaD_reallocstack(L, L->stacksize/2); /* still big enough... */ condhardstacktests(luaD_reallocstack(L, s_used)); } static void traversestack (global_State *g, lua_State *l) { StkId o, lim; CallInfo *ci; markvalue(g, gt(l)); lim = l->top; for (ci = l->base_ci; ci <= l->ci; ci++) { lua_assert(ci->top <= l->stack_last); if (lim < ci->top) lim = ci->top; } for (o = l->stack; o < l->top; o++) markvalue(g, o); for (; o <= lim; o++) setnilvalue(o); checkstacksizes(l, lim); } /* ** traverse one gray object, turning it to black. ** Returns `quantity' traversed. */ static l_mem propagatemark (global_State *g) { GCObject *o = g->gray; lua_assert(isgray(o)); gray2black(o); switch (o->gch.tt) { case LUA_TTABLE: { Table *h = gco2h(o); g->gray = h->gclist; if (traversetable(g, h)) /* table is weak? */ black2gray(o); /* keep it gray */ return sizeof(Table) + sizeof(TValue) * h->sizearray + sizeof(Node) * sizenode(h); } case LUA_TFUNCTION: { Closure *cl = gco2cl(o); g->gray = cl->c.gclist; traverseclosure(g, cl); return (cl->c.isC) ? sizeCclosure(cl->c.nupvalues) : sizeLclosure(cl->l.nupvalues); } case LUA_TTHREAD: { lua_State *th = gco2th(o); g->gray = th->gclist; th->gclist = g->grayagain; g->grayagain = o; black2gray(o); traversestack(g, th); return sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci; } case LUA_TPROTO: { Proto *p = gco2p(o); g->gray = p->gclist; traverseproto(g, p); return sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto *) * p->sizep + sizeof(TValue) * p->sizek + sizeof(int) * p->sizelineinfo + sizeof(LocVar) * p->sizelocvars + sizeof(TString *) * p->sizeupvalues; } default: lua_assert(0); return 0; } } static size_t propagateall (global_State *g) { size_t m = 0; while (g->gray) m += propagatemark(g); return m; } /* ** The next function tells whether a key or value can be cleared from ** a weak table. Non-collectable objects are never removed from weak ** tables. Strings behave as `values', so are never removed too. for ** other objects: if really collected, cannot keep them; for userdata ** being finalized, keep them in keys, but not in values */ static int iscleared (const TValue *o, int iskey) { if (!iscollectable(o)) return 0; if (ttisstring(o)) { stringmark(rawtsvalue(o)); /* strings are `values', so are never weak */ return 0; } return iswhite(gcvalue(o)) || (ttisuserdata(o) && (!iskey && isfinalized(uvalue(o)))); } /* ** clear collected entries from weaktables */ static void cleartable (GCObject *l) { while (l) { Table *h = gco2h(l); int i = h->sizearray; lua_assert(testbit(h->marked, VALUEWEAKBIT) || testbit(h->marked, KEYWEAKBIT)); if (testbit(h->marked, VALUEWEAKBIT)) { while (i--) { TValue *o = &h->array[i]; if (iscleared(o, 0)) /* value was collected? */ setnilvalue(o); /* remove value */ } } i = sizenode(h); while (i--) { Node *n = gnode(h, i); if (!ttisnil(gval(n)) && /* non-empty entry? */ (iscleared(key2tval(n), 1) || iscleared(gval(n), 0))) { setnilvalue(gval(n)); /* remove value ... */ removeentry(n); /* remove entry from table */ } } l = h->gclist; } } static void freeobj (lua_State *L, GCObject *o) { switch (o->gch.tt) { case LUA_TPROTO: luaF_freeproto(L, gco2p(o)); break; case LUA_TFUNCTION: luaF_freeclosure(L, gco2cl(o)); break; case LUA_TUPVAL: luaF_freeupval(L, gco2uv(o)); break; case LUA_TTABLE: luaH_free(L, gco2h(o)); break; case LUA_TTHREAD: { lua_assert(gco2th(o) != L && gco2th(o) != G(L)->mainthread); luaE_freethread(L, gco2th(o)); break; } case LUA_TSTRING: { G(L)->strt.nuse--; luaM_freemem(L, o, sizestring(gco2ts(o))); break; } case LUA_TUSERDATA: { luaM_freemem(L, o, sizeudata(gco2u(o))); break; } default: lua_assert(0); } } #define sweepwholelist(L,p) sweeplist(L,p,MAX_LUMEM) static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count) { GCObject *curr; global_State *g = G(L); int deadmask = otherwhite(g); while ((curr = *p) != NULL && count-- > 0) { if (curr->gch.tt == LUA_TTHREAD) /* sweep open upvalues of each thread */ sweepwholelist(L, &gco2th(curr)->openupval); if ((curr->gch.marked ^ WHITEBITS) & deadmask) { /* not dead? */ lua_assert(!isdead(g, curr) || testbit(curr->gch.marked, FIXEDBIT)); makewhite(g, curr); /* make it white (for next cycle) */ p = &curr->gch.next; } else { /* must erase `curr' */ lua_assert(isdead(g, curr) || deadmask == bitmask(SFIXEDBIT)); *p = curr->gch.next; if (curr == g->rootgc) /* is the first element of the list? */ g->rootgc = curr->gch.next; /* adjust first */ freeobj(L, curr); } } return p; } static void checkSizes (lua_State *L) { global_State *g = G(L); /* check size of string hash */ if (g->strt.nuse < cast(lu_int32, g->strt.size/4) && g->strt.size > MINSTRTABSIZE*2) luaS_resize(L, g->strt.size/2); /* table is too big */ /* check size of buffer */ if (luaZ_sizebuffer(&g->buff) > LUA_MINBUFFER*2) { /* buffer too big? */ size_t newsize = luaZ_sizebuffer(&g->buff) / 2; luaZ_resizebuffer(L, &g->buff, newsize); } } static void GCTM (lua_State *L) { global_State *g = G(L); GCObject *o = g->tmudata->gch.next; /* get first element */ Udata *udata = rawgco2u(o); const TValue *tm; /* remove udata from `tmudata' */ if (o == g->tmudata) /* last element? */ g->tmudata = NULL; else g->tmudata->gch.next = udata->uv.next; udata->uv.next = g->mainthread->next; /* return it to `root' list */ g->mainthread->next = o; makewhite(g, o); tm = fasttm(L, udata->uv.metatable, TM_GC); if (tm != NULL) { lu_byte oldah = L->allowhook; lu_mem oldt = g->GCthreshold; L->allowhook = 0; /* stop debug hooks during GC tag method */ g->GCthreshold = 2*g->totalbytes; /* avoid GC steps */ setobj2s(L, L->top, tm); setuvalue(L, L->top+1, udata); L->top += 2; luaD_call(L, L->top - 2, 0); L->allowhook = oldah; /* restore hooks */ g->GCthreshold = oldt; /* restore threshold */ } } /* ** Call all GC tag methods */ void luaC_callGCTM (lua_State *L) { while (G(L)->tmudata) GCTM(L); } void luaC_freeall (lua_State *L) { global_State *g = G(L); int i; g->currentwhite = WHITEBITS | bitmask(SFIXEDBIT); /* mask to collect all elements */ sweepwholelist(L, &g->rootgc); for (i = 0; i < g->strt.size; i++) /* free all string lists */ sweepwholelist(L, &g->strt.hash[i]); } static void markmt (global_State *g) { int i; for (i=0; imt[i]) markobject(g, g->mt[i]); } /* mark root set */ static void markroot (lua_State *L) { global_State *g = G(L); g->gray = NULL; g->grayagain = NULL; g->weak = NULL; markobject(g, g->mainthread); /* make global table be traversed before main stack */ markvalue(g, gt(g->mainthread)); markvalue(g, registry(L)); markmt(g); g->gcstate = GCSpropagate; } static void remarkupvals (global_State *g) { UpVal *uv; for (uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next) { lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); if (isgray(obj2gco(uv))) markvalue(g, uv->v); } } static void atomic (lua_State *L) { global_State *g = G(L); size_t udsize; /* total size of userdata to be finalized */ /* remark occasional upvalues of (maybe) dead threads */ remarkupvals(g); /* traverse objects cautch by write barrier and by 'remarkupvals' */ propagateall(g); /* remark weak tables */ g->gray = g->weak; g->weak = NULL; lua_assert(!iswhite(obj2gco(g->mainthread))); markobject(g, L); /* mark running thread */ markmt(g); /* mark basic metatables (again) */ propagateall(g); /* remark gray again */ g->gray = g->grayagain; g->grayagain = NULL; propagateall(g); udsize = luaC_separateudata(L, 0); /* separate userdata to be finalized */ marktmu(g); /* mark `preserved' userdata */ udsize += propagateall(g); /* remark, to propagate `preserveness' */ cleartable(g->weak); /* remove collected objects from weak tables */ /* flip current white */ g->currentwhite = cast_byte(otherwhite(g)); g->sweepstrgc = 0; g->sweepgc = &g->rootgc; g->gcstate = GCSsweepstring; g->estimate = g->totalbytes - udsize; /* first estimate */ } static l_mem singlestep (lua_State *L) { global_State *g = G(L); /*lua_checkmemory(L);*/ switch (g->gcstate) { case GCSpause: { markroot(L); /* start a new collection */ return 0; } case GCSpropagate: { if (g->gray) return propagatemark(g); else { /* no more `gray' objects */ atomic(L); /* finish mark phase */ return 0; } } case GCSsweepstring: { lu_mem old = g->totalbytes; sweepwholelist(L, &g->strt.hash[g->sweepstrgc++]); if (g->sweepstrgc >= g->strt.size) /* nothing more to sweep? */ g->gcstate = GCSsweep; /* end sweep-string phase */ lua_assert(old >= g->totalbytes); g->estimate -= old - g->totalbytes; return GCSWEEPCOST; } case GCSsweep: { lu_mem old = g->totalbytes; g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX); if (*g->sweepgc == NULL) { /* nothing more to sweep? */ checkSizes(L); g->gcstate = GCSfinalize; /* end sweep phase */ } lua_assert(old >= g->totalbytes); g->estimate -= old - g->totalbytes; return GCSWEEPMAX*GCSWEEPCOST; } case GCSfinalize: { if (g->tmudata) { GCTM(L); if (g->estimate > GCFINALIZECOST) g->estimate -= GCFINALIZECOST; return GCFINALIZECOST; } else { g->gcstate = GCSpause; /* end collection */ g->gcdept = 0; return 0; } } default: lua_assert(0); return 0; } } void luaC_step (lua_State *L) { global_State *g = G(L); l_mem lim = (GCSTEPSIZE/100) * g->gcstepmul; if (lim == 0) lim = (MAX_LUMEM-1)/2; /* no limit */ g->gcdept += g->totalbytes - g->GCthreshold; do { lim -= singlestep(L); if (g->gcstate == GCSpause) break; } while (lim > 0); if (g->gcstate != GCSpause) { if (g->gcdept < GCSTEPSIZE) g->GCthreshold = g->totalbytes + GCSTEPSIZE; /* - lim/g->gcstepmul;*/ else { g->gcdept -= GCSTEPSIZE; g->GCthreshold = g->totalbytes; } } else { setthreshold(g); } } void luaC_fullgc (lua_State *L) { global_State *g = G(L); if (g->gcstate <= GCSpropagate) { /* reset sweep marks to sweep all elements (returning them to white) */ g->sweepstrgc = 0; g->sweepgc = &g->rootgc; /* reset other collector lists */ g->gray = NULL; g->grayagain = NULL; g->weak = NULL; g->gcstate = GCSsweepstring; } lua_assert(g->gcstate != GCSpause && g->gcstate != GCSpropagate); /* finish any pending sweep phase */ while (g->gcstate != GCSfinalize) { lua_assert(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep); singlestep(L); } markroot(L); while (g->gcstate != GCSpause) { singlestep(L); } setthreshold(g); } void luaC_barrierf (lua_State *L, GCObject *o, GCObject *v) { global_State *g = G(L); lua_assert(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o)); lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause); lua_assert(ttype(&o->gch) != LUA_TTABLE); /* must keep invariant? */ if (g->gcstate == GCSpropagate) reallymarkobject(g, v); /* restore invariant */ else /* don't mind */ makewhite(g, o); /* mark as white just to avoid other barriers */ } void luaC_barrierback (lua_State *L, Table *t) { global_State *g = G(L); GCObject *o = obj2gco(t); lua_assert(isblack(o) && !isdead(g, o)); lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause); black2gray(o); /* make table gray (again) */ t->gclist = g->grayagain; g->grayagain = o; } void luaC_link (lua_State *L, GCObject *o, lu_byte tt) { global_State *g = G(L); o->gch.next = g->rootgc; g->rootgc = o; o->gch.marked = luaC_white(g); o->gch.tt = tt; } void luaC_linkupval (lua_State *L, UpVal *uv) { global_State *g = G(L); GCObject *o = obj2gco(uv); o->gch.next = g->rootgc; /* link upvalue into `rootgc' list */ g->rootgc = o; if (isgray(o)) { if (g->gcstate == GCSpropagate) { gray2black(o); /* closed upvalues need barrier */ luaC_barrier(L, uv, uv->v); } else { /* sweep phase: sweep it (turning it into white) */ makewhite(g, o); lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause); } } } ================================================ FILE: src/lgc.h ================================================ /* ** $Id: lgc.h,v 2.15.1.1 2007/12/27 13:02:25 roberto Exp $ ** Garbage Collector ** See Copyright Notice in lua.h */ #ifndef lgc_h #define lgc_h #include "lobject.h" /* ** Possible states of the Garbage Collector */ #define GCSpause 0 #define GCSpropagate 1 #define GCSsweepstring 2 #define GCSsweep 3 #define GCSfinalize 4 /* ** some userful bit tricks */ #define resetbits(x,m) ((x) &= cast(lu_byte, ~(m))) #define setbits(x,m) ((x) |= (m)) #define testbits(x,m) ((x) & (m)) #define bitmask(b) (1<<(b)) #define bit2mask(b1,b2) (bitmask(b1) | bitmask(b2)) #define l_setbit(x,b) setbits(x, bitmask(b)) #define resetbit(x,b) resetbits(x, bitmask(b)) #define testbit(x,b) testbits(x, bitmask(b)) #define set2bits(x,b1,b2) setbits(x, (bit2mask(b1, b2))) #define reset2bits(x,b1,b2) resetbits(x, (bit2mask(b1, b2))) #define test2bits(x,b1,b2) testbits(x, (bit2mask(b1, b2))) /* ** Layout for bit use in `marked' field: ** bit 0 - object is white (type 0) ** bit 1 - object is white (type 1) ** bit 2 - object is black ** bit 3 - for userdata: has been finalized ** bit 3 - for tables: has weak keys ** bit 4 - for tables: has weak values ** bit 5 - object is fixed (should not be collected) ** bit 6 - object is "super" fixed (only the main thread) */ #define WHITE0BIT 0 #define WHITE1BIT 1 #define BLACKBIT 2 #define FINALIZEDBIT 3 #define KEYWEAKBIT 3 #define VALUEWEAKBIT 4 #define FIXEDBIT 5 #define SFIXEDBIT 6 #define WHITEBITS bit2mask(WHITE0BIT, WHITE1BIT) #define iswhite(x) test2bits((x)->gch.marked, WHITE0BIT, WHITE1BIT) #define isblack(x) testbit((x)->gch.marked, BLACKBIT) #define isgray(x) (!isblack(x) && !iswhite(x)) #define otherwhite(g) (g->currentwhite ^ WHITEBITS) #define isdead(g,v) ((v)->gch.marked & otherwhite(g) & WHITEBITS) #define changewhite(x) ((x)->gch.marked ^= WHITEBITS) #define gray2black(x) l_setbit((x)->gch.marked, BLACKBIT) #define valiswhite(x) (iscollectable(x) && iswhite(gcvalue(x))) #define luaC_white(g) cast(lu_byte, (g)->currentwhite & WHITEBITS) #define luaC_checkGC(L) { \ condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1)); \ if (G(L)->totalbytes >= G(L)->GCthreshold) \ luaC_step(L); } #define luaC_barrier(L,p,v) { if (valiswhite(v) && isblack(obj2gco(p))) \ luaC_barrierf(L,obj2gco(p),gcvalue(v)); } #define luaC_barriert(L,t,v) { if (valiswhite(v) && isblack(obj2gco(t))) \ luaC_barrierback(L,t); } #define luaC_objbarrier(L,p,o) \ { if (iswhite(obj2gco(o)) && isblack(obj2gco(p))) \ luaC_barrierf(L,obj2gco(p),obj2gco(o)); } #define luaC_objbarriert(L,t,o) \ { if (iswhite(obj2gco(o)) && isblack(obj2gco(t))) luaC_barrierback(L,t); } LUAI_FUNC size_t luaC_separateudata (lua_State *L, int all); LUAI_FUNC void luaC_callGCTM (lua_State *L); LUAI_FUNC void luaC_freeall (lua_State *L); LUAI_FUNC void luaC_step (lua_State *L); LUAI_FUNC void luaC_fullgc (lua_State *L); LUAI_FUNC void luaC_link (lua_State *L, GCObject *o, lu_byte tt); LUAI_FUNC void luaC_linkupval (lua_State *L, UpVal *uv); LUAI_FUNC void luaC_barrierf (lua_State *L, GCObject *o, GCObject *v); LUAI_FUNC void luaC_barrierback (lua_State *L, Table *t); #endif ================================================ FILE: src/linit.c ================================================ /* ** $Id: linit.c,v 1.14.1.1 2007/12/27 13:02:25 roberto Exp $ ** Initialization of libraries for lua.c ** See Copyright Notice in lua.h */ #define linit_c #define LUA_LIB #include "lua.h" #include "lualib.h" #include "lauxlib.h" static const luaL_Reg lualibs[] = { {"", luaopen_base}, {LUA_TABLIBNAME, luaopen_table}, {LUA_IOLIBNAME, luaopen_io}, {LUA_OSLIBNAME, luaopen_os}, {LUA_STRLIBNAME, luaopen_string}, {LUA_MATHLIBNAME, luaopen_math}, {LUA_CURSESLIBNAME, luaopen_curses}, {LUA_SOCKETCORELIBNAME, luaopen_socket_core}, {LUA_MIMECORELIBNAME, luaopen_mime_core}, {LUA_SSLLIBNAME, luaopen_ssl_core}, {LUA_SSLCONTEXTLIBNAME, luaopen_ssl_context}, {LUA_SSLX509LIBNAME, luaopen_ssl_x509}, {LUA_SSLCONFIGLIBNAME, luaopen_ssl_config}, {NULL, NULL} }; LUALIB_API void luaL_openlibs (lua_State *L) { const luaL_Reg *lib = lualibs; for (; lib->func; lib++) { lua_pushcfunction(L, lib->func); lua_pushstring(L, lib->name); lua_call(L, 1, 0); } } ================================================ FILE: src/liolib.c ================================================ /* ** $Id: liolib.c,v 2.73.1.4 2010/05/14 15:33:51 roberto Exp $ ** Standard I/O (and system) library ** See Copyright Notice in lua.h */ #include #include #include #include #include #define liolib_c #define LUA_LIB #include "lua.h" #include "lauxlib.h" #include "lualib.h" #include "teliva.h" static int pushresult (lua_State *L, int i, const char *filename) { int en = errno; /* calls to Lua API may change this value */ if (i) { lua_pushboolean(L, 1); return 1; } else { lua_pushnil(L); if (filename) lua_pushfstring(L, "%s: %s", filename, strerror(en)); else lua_pushfstring(L, "%s", strerror(en)); lua_pushinteger(L, en); return 3; } } #define tofilep(L) ((FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE)) static int io_type (lua_State *L) { void *ud; luaL_checkany(L, 1); ud = lua_touserdata(L, 1); lua_getfield(L, LUA_REGISTRYINDEX, LUA_FILEHANDLE); if (ud == NULL || !lua_getmetatable(L, 1) || !lua_rawequal(L, -2, -1)) lua_pushnil(L); /* not a file */ else if (*((FILE **)ud) == NULL) lua_pushliteral(L, "closed file"); else lua_pushliteral(L, "file"); return 1; } static FILE *tofile (lua_State *L) { FILE **f = tofilep(L); if (*f == NULL) luaL_error(L, "attempt to use a closed file"); return *f; } /* ** When creating file handles, always creates a `closed' file handle ** before opening the actual file; so, if there is a memory error, the ** file is not left opened. */ static FILE **newfile (lua_State *L) { FILE **pf = (FILE **)lua_newuserdata(L, sizeof(FILE *)); *pf = NULL; /* file handle is currently `closed' */ luaL_getmetatable(L, LUA_FILEHANDLE); lua_setmetatable(L, -2); return pf; } /* ** function to close regular files */ static int io_fclose (lua_State *L) { FILE **p = tofilep(L); int ok = (fclose(*p) == 0); *p = NULL; return pushresult(L, ok, NULL); } static int aux_close (lua_State *L) { lua_getfenv(L, 1); lua_getfield(L, -1, "__close"); return (lua_tocfunction(L, -1))(L); } static int io_close (lua_State *L) { tofile(L); /* make sure argument is a file */ return aux_close(L); } static int io_gc (lua_State *L) { FILE *f = *tofilep(L); /* ignore closed files */ if (f != NULL) aux_close(L); return 0; } static int io_tostring (lua_State *L) { FILE *f = *tofilep(L); if (f == NULL) lua_pushliteral(L, "file (closed)"); else lua_pushfstring(L, "file (%p)", f); return 1; } static char iolib_errbuf[1024] = {0}; static int io_open (lua_State *L) { const char *filename = luaL_checkstring(L, 1); const char *mode = luaL_optstring(L, 2, "r"); static char buffer[1024] = {0}; memset(buffer, '\0', 1024); snprintf(buffer, 1020, "io.open(\"%s\", \"%s\")", filename, mode); append_to_audit_log(L, buffer); FILE **pf = newfile(L); /* filenames starting with teliva_tmp_ are always ok */ if (starts_with(filename, "teliva_tmp_")) { *pf = fopen(filename, mode); } /* other filenames starting with teliva_ are never ok (reserved for the * framework, should not be accessed by apps directly */ else if (starts_with(filename, "teliva_")) { snprintf(iolib_errbuf, 1024, "app tried to open file '%s'; relative paths are never allowed", filename); Previous_message = iolib_errbuf; } else if (contains(filename, "./")) { snprintf(iolib_errbuf, 1024, "app tried to open file '%s'; relative paths are never allowed", filename); Previous_message = iolib_errbuf; } else if (file_operation_permitted(filename, mode)) { *pf = fopen(filename, mode); } else { snprintf(iolib_errbuf, 1024, "app tried to open file '%s'; adjust its permissions (ctrl-p) if that is expected", filename); Previous_message = iolib_errbuf; } return (*pf == NULL) ? pushresult(L, 0, filename) : 1; } static int io_tmpfile (lua_State *L) { FILE **pf = newfile(L); *pf = tmpfile(); return (*pf == NULL) ? pushresult(L, 0, NULL) : 1; } static int io_readline (lua_State *L); static void aux_lines (lua_State *L, int idx, int toclose) { lua_pushvalue(L, idx); lua_pushboolean(L, toclose); /* close/not close file when finished */ lua_pushcclosure(L, io_readline, 2); } static int f_lines (lua_State *L) { tofile(L); /* check that it's a valid file handle */ aux_lines(L, 1, 0); return 1; } /* ** {====================================================== ** READ ** ======================================================= */ static int read_number (lua_State *L, FILE *f) { lua_Number d; if (fscanf(f, LUA_NUMBER_SCAN, &d) == 1) { lua_pushnumber(L, d); return 1; } else { lua_pushnil(L); /* "result" to be removed */ return 0; /* read fails */ } } static int test_eof (lua_State *L, FILE *f) { int c = getc(f); ungetc(c, f); lua_pushlstring(L, NULL, 0); return (c != EOF); } static int read_line (lua_State *L, FILE *f) { luaL_Buffer b; luaL_buffinit(L, &b); for (;;) { size_t l; char *p = luaL_prepbuffer(&b); if (fgets(p, LUAL_BUFFERSIZE, f) == NULL) { /* eof? */ luaL_pushresult(&b); /* close buffer */ return (lua_objlen(L, -1) > 0); /* check whether read something */ } l = strlen(p); if (l == 0 || p[l-1] != '\n') luaL_addsize(&b, l); else { luaL_addsize(&b, l - 1); /* do not include `eol' */ luaL_pushresult(&b); /* close buffer */ return 1; /* read at least an `eol' */ } } } static int read_chars (lua_State *L, FILE *f, size_t n) { size_t rlen; /* how much to read */ size_t nr; /* number of chars actually read */ luaL_Buffer b; luaL_buffinit(L, &b); rlen = LUAL_BUFFERSIZE; /* try to read that much each time */ do { char *p = luaL_prepbuffer(&b); if (rlen > n) rlen = n; /* cannot read more than asked */ nr = fread(p, sizeof(char), rlen, f); luaL_addsize(&b, nr); n -= nr; /* still have to read `n' chars */ } while (n > 0 && nr == rlen); /* until end of count or eof */ luaL_pushresult(&b); /* close buffer */ return (n == 0 || lua_objlen(L, -1) > 0); } static int g_read (lua_State *L, FILE *f, int first) { int nargs = lua_gettop(L) - 1; int success; int n; clearerr(f); if (nargs == 0) { /* no arguments? */ success = read_line(L, f); n = first+1; /* to return 1 result */ } else { /* ensure stack space for all results and for auxlib's buffer */ luaL_checkstack(L, nargs+LUA_MINSTACK, "too many arguments"); success = 1; for (n = first; nargs-- && success; n++) { if (lua_type(L, n) == LUA_TNUMBER) { size_t l = (size_t)lua_tointeger(L, n); success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l); } else { const char *p = lua_tostring(L, n); luaL_argcheck(L, p && p[0] == '*', n, "invalid option"); switch (p[1]) { case 'n': /* number */ success = read_number(L, f); break; case 'l': /* line */ success = read_line(L, f); break; case 'a': /* file */ read_chars(L, f, ~((size_t)0)); /* read MAX_SIZE_T chars */ success = 1; /* always success */ break; default: return luaL_argerror(L, n, "invalid format"); } } } } if (ferror(f)) return pushresult(L, 0, NULL); if (!success) { lua_pop(L, 1); /* remove last result */ lua_pushnil(L); /* push nil instead */ } return n - first; } static int f_read (lua_State *L) { return g_read(L, tofile(L), 2); } static int io_readline (lua_State *L) { FILE *f = *(FILE **)lua_touserdata(L, lua_upvalueindex(1)); int sucess; if (f == NULL) /* file is already closed? */ luaL_error(L, "file is already closed"); sucess = read_line(L, f); if (ferror(f)) return luaL_error(L, "%s", strerror(errno)); if (sucess) return 1; else { /* EOF */ if (lua_toboolean(L, lua_upvalueindex(2))) { /* generator created file? */ lua_settop(L, 0); lua_pushvalue(L, lua_upvalueindex(1)); aux_close(L); /* close it */ } return 0; } } /* }====================================================== */ static int g_write (lua_State *L, FILE *f, int arg) { int nargs = lua_gettop(L) - 1; int status = 1; for (; nargs--; arg++) { if (lua_type(L, arg) == LUA_TNUMBER) { /* optimization: could be done exactly as for strings */ status = status && fprintf(f, LUA_NUMBER_FMT, lua_tonumber(L, arg)) > 0; } else { size_t l; const char *s = luaL_checklstring(L, arg, &l); status = status && (fwrite(s, sizeof(char), l, f) == l); } } return pushresult(L, status, NULL); } static int f_write (lua_State *L) { return g_write(L, tofile(L), 2); } static int f_seek (lua_State *L) { static const int mode[] = {SEEK_SET, SEEK_CUR, SEEK_END}; static const char *const modenames[] = {"set", "cur", "end", NULL}; FILE *f = tofile(L); int op = luaL_checkoption(L, 2, "cur", modenames); long offset = luaL_optlong(L, 3, 0); op = fseek(f, offset, mode[op]); if (op) return pushresult(L, 0, NULL); /* error */ else { lua_pushinteger(L, ftell(f)); return 1; } } static int f_setvbuf (lua_State *L) { static const int mode[] = {_IONBF, _IOFBF, _IOLBF}; static const char *const modenames[] = {"no", "full", "line", NULL}; FILE *f = tofile(L); int op = luaL_checkoption(L, 2, NULL, modenames); lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE); int res = setvbuf(f, NULL, mode[op], sz); return pushresult(L, res == 0, NULL); } static int f_flush (lua_State *L) { return pushresult(L, fflush(tofile(L)) == 0, NULL); } static const luaL_Reg iolib[] = { {"close", io_close}, /* no 'flush' since Teliva is ncurses-based */ /* no 'input' since Teliva is ncurses-based */ /* no 'io.lines'; it can confusingly fail without showing sandboxing errors */ {"open", io_open}, /* no 'output' since Teliva is ncurses-based */ /* no 'popen' without sandboxing it */ /* no 'read' since Teliva is ncurses-based */ {"tmpfile", io_tmpfile}, {"type", io_type}, /* no 'write' since Teliva is ncurses-based */ {NULL, NULL} }; static const luaL_Reg flib[] = { {"close", io_close}, {"flush", f_flush}, {"lines", f_lines}, {"read", f_read}, {"seek", f_seek}, {"setvbuf", f_setvbuf}, {"write", f_write}, {"__gc", io_gc}, {"__tostring", io_tostring}, {NULL, NULL} }; static void createmeta (lua_State *L) { luaL_newmetatable(L, LUA_FILEHANDLE); /* create metatable for file handles */ lua_pushvalue(L, -1); /* push metatable */ lua_setfield(L, -2, "__index"); /* metatable.__index = metatable */ luaL_register(L, NULL, flib); /* file methods */ } static void newfenv (lua_State *L, lua_CFunction cls) { lua_createtable(L, 0, 1); lua_pushcfunction(L, cls); lua_setfield(L, -2, "__close"); } LUALIB_API int luaopen_io (lua_State *L) { createmeta(L); newfenv(L, io_fclose); lua_replace(L, LUA_ENVIRONINDEX); /* open library */ luaL_register(L, LUA_IOLIBNAME, iolib); return 1; } ================================================ FILE: src/llex.c ================================================ /* ** $Id: llex.c,v 2.20.1.2 2009/11/23 14:58:22 roberto Exp $ ** Lexical Analyzer ** See Copyright Notice in lua.h */ #include #include #include #define llex_c #define LUA_CORE #include "lua.h" #include "ldo.h" #include "llex.h" #include "lobject.h" #include "lparser.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "lzio.h" #define next(ls) (ls->current = zgetc(ls->z)) #define currIsNewline(ls) (ls->current == '\n' || ls->current == '\r') /* ORDER RESERVED */ const char *const luaX_tokens [] = { "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while", "..", "...", "==", ">=", "<=", "~=", "", "", "", "", NULL }; #define save_and_next(ls) (save(ls, ls->current), next(ls)) static void save (LexState *ls, int c) { Mbuffer *b = ls->buff; if (b->n + 1 > b->buffsize) { size_t newsize; if (b->buffsize >= MAX_SIZET/2) luaX_lexerror(ls, "lexical element too long", 0); newsize = b->buffsize * 2; luaZ_resizebuffer(ls->L, b, newsize); } b->buffer[b->n++] = cast(char, c); } void luaX_init (lua_State *L) { int i; for (i=0; itsv.reserved = cast_byte(i+1); /* reserved word */ } } #define MAXSRC 80 const char *luaX_token2str (LexState *ls, int token) { if (token < FIRST_RESERVED) { lua_assert(token == cast(unsigned char, token)); return (iscntrl(token)) ? luaO_pushfstring(ls->L, "char(%d)", token) : luaO_pushfstring(ls->L, "%c", token); } else return luaX_tokens[token-FIRST_RESERVED]; } static const char *txtToken (LexState *ls, int token) { switch (token) { case TK_NAME: case TK_STRING: case TK_NUMBER: save(ls, '\0'); return luaZ_buffer(ls->buff); default: return luaX_token2str(ls, token); } } void luaX_lexerror (LexState *ls, const char *msg, int token) { char buff[MAXSRC]; luaO_chunkid(buff, getstr(ls->source), MAXSRC); msg = luaO_pushfstring(ls->L, "%s:%d: %s", buff, ls->linenumber, msg); if (token) luaO_pushfstring(ls->L, "%s near " LUA_QS, msg, txtToken(ls, token)); luaD_throw(ls->L, LUA_ERRSYNTAX); } void luaX_syntaxerror (LexState *ls, const char *msg) { luaX_lexerror(ls, msg, ls->t.token); } TString *luaX_newstring (LexState *ls, const char *str, size_t l) { lua_State *L = ls->L; TString *ts = luaS_newlstr(L, str, l); TValue *o = luaH_setstr(L, ls->fs->h, ts); /* entry for `str' */ if (ttisnil(o)) { setbvalue(o, 1); /* make sure `str' will not be collected */ luaC_checkGC(L); } return ts; } static void inclinenumber (LexState *ls) { int old = ls->current; lua_assert(currIsNewline(ls)); next(ls); /* skip `\n' or `\r' */ if (currIsNewline(ls) && ls->current != old) next(ls); /* skip `\n\r' or `\r\n' */ if (++ls->linenumber >= MAX_INT) luaX_syntaxerror(ls, "chunk has too many lines"); } void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source) { ls->decpoint = '.'; ls->L = L; ls->lookahead.token = TK_EOS; /* no look-ahead token */ ls->z = z; ls->fs = NULL; ls->linenumber = 1; ls->lastline = 1; ls->source = source; luaZ_resizebuffer(ls->L, ls->buff, LUA_MINBUFFER); /* initialize buffer */ next(ls); /* read first char */ } /* ** ======================================================= ** LEXICAL ANALYZER ** ======================================================= */ static int check_next (LexState *ls, const char *set) { if (!strchr(set, ls->current)) return 0; save_and_next(ls); return 1; } static void buffreplace (LexState *ls, char from, char to) { size_t n = luaZ_bufflen(ls->buff); char *p = luaZ_buffer(ls->buff); while (n--) if (p[n] == from) p[n] = to; } static void trydecpoint (LexState *ls, SemInfo *seminfo) { /* format error: try to update decimal point separator */ struct lconv *cv = localeconv(); char old = ls->decpoint; ls->decpoint = (cv ? cv->decimal_point[0] : '.'); buffreplace(ls, old, ls->decpoint); /* try updated decimal separator */ if (!luaO_str2d(luaZ_buffer(ls->buff), &seminfo->r)) { /* format error with correct decimal point: no more options */ buffreplace(ls, ls->decpoint, '.'); /* undo change (for error message) */ luaX_lexerror(ls, "malformed number", TK_NUMBER); } } /* LUA_NUMBER */ static void read_numeral (LexState *ls, SemInfo *seminfo) { lua_assert(isdigit(ls->current)); do { save_and_next(ls); } while (isdigit(ls->current) || ls->current == '.'); if (check_next(ls, "Ee")) /* `E'? */ check_next(ls, "+-"); /* optional exponent sign */ while (isalnum(ls->current) || ls->current == '_') save_and_next(ls); save(ls, '\0'); buffreplace(ls, '.', ls->decpoint); /* follow locale for decimal point */ if (!luaO_str2d(luaZ_buffer(ls->buff), &seminfo->r)) /* format error? */ trydecpoint(ls, seminfo); /* try to update decimal point separator */ } static int skip_sep (LexState *ls) { int count = 0; int s = ls->current; lua_assert(s == '[' || s == ']'); save_and_next(ls); while (ls->current == '=') { save_and_next(ls); count++; } return (ls->current == s) ? count : (-count) - 1; } static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) { int cont = 0; (void)(cont); /* avoid warnings when `cont' is not used */ save_and_next(ls); /* skip 2nd `[' */ if (currIsNewline(ls)) /* string starts with a newline? */ inclinenumber(ls); /* skip it */ for (;;) { switch (ls->current) { case EOZ: luaX_lexerror(ls, (seminfo) ? "unfinished long string" : "unfinished long comment", TK_EOS); break; /* to avoid warnings */ #if defined(LUA_COMPAT_LSTR) case '[': { if (skip_sep(ls) == sep) { save_and_next(ls); /* skip 2nd `[' */ cont++; #if LUA_COMPAT_LSTR == 1 if (sep == 0) luaX_lexerror(ls, "nesting of [[...]] is deprecated", '['); #endif } break; } #endif case ']': { if (skip_sep(ls) == sep) { save_and_next(ls); /* skip 2nd `]' */ #if defined(LUA_COMPAT_LSTR) && LUA_COMPAT_LSTR == 2 cont--; if (sep == 0 && cont >= 0) break; #endif goto endloop; } break; } case '\n': case '\r': { save(ls, '\n'); inclinenumber(ls); if (!seminfo) luaZ_resetbuffer(ls->buff); /* avoid wasting space */ break; } default: { if (seminfo) save_and_next(ls); else next(ls); } } } endloop: if (seminfo) seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + (2 + sep), luaZ_bufflen(ls->buff) - 2*(2 + sep)); } static void read_string (LexState *ls, int del, SemInfo *seminfo) { save_and_next(ls); while (ls->current != del) { switch (ls->current) { case EOZ: luaX_lexerror(ls, "unfinished string", TK_EOS); continue; /* to avoid warnings */ case '\n': case '\r': luaX_lexerror(ls, "unfinished string", TK_STRING); continue; /* to avoid warnings */ case '\\': { int c; next(ls); /* do not save the `\' */ switch (ls->current) { case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case '\n': /* go through */ case '\r': save(ls, '\n'); inclinenumber(ls); continue; case EOZ: continue; /* will raise an error next loop */ default: { if (!isdigit(ls->current)) save_and_next(ls); /* handles \\, \", \', and \? */ else { /* \xxx */ int i = 0; c = 0; do { c = 10*c + (ls->current-'0'); next(ls); } while (++i<3 && isdigit(ls->current)); if (c > UCHAR_MAX) luaX_lexerror(ls, "escape sequence too large", TK_STRING); save(ls, c); } continue; } } save(ls, c); next(ls); continue; } default: save_and_next(ls); } } save_and_next(ls); /* skip delimiter */ seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + 1, luaZ_bufflen(ls->buff) - 2); } static int llex (LexState *ls, SemInfo *seminfo) { luaZ_resetbuffer(ls->buff); for (;;) { switch (ls->current) { case '\n': case '\r': { inclinenumber(ls); continue; } case '-': { next(ls); if (ls->current != '-') return '-'; /* else is a comment */ next(ls); if (ls->current == '[') { int sep = skip_sep(ls); luaZ_resetbuffer(ls->buff); /* `skip_sep' may dirty the buffer */ if (sep >= 0) { read_long_string(ls, NULL, sep); /* long comment */ luaZ_resetbuffer(ls->buff); continue; } } /* else short comment */ while (!currIsNewline(ls) && ls->current != EOZ) next(ls); continue; } case '[': { int sep = skip_sep(ls); if (sep >= 0) { read_long_string(ls, seminfo, sep); return TK_STRING; } else if (sep == -1) return '['; else luaX_lexerror(ls, "invalid long string delimiter", TK_STRING); } case '=': { next(ls); if (ls->current != '=') return '='; else { next(ls); return TK_EQ; } } case '<': { next(ls); if (ls->current != '=') return '<'; else { next(ls); return TK_LE; } } case '>': { next(ls); if (ls->current != '=') return '>'; else { next(ls); return TK_GE; } } case '~': { next(ls); if (ls->current != '=') return '~'; else { next(ls); return TK_NE; } } case '"': case '\'': { read_string(ls, ls->current, seminfo); return TK_STRING; } case '.': { save_and_next(ls); if (check_next(ls, ".")) { if (check_next(ls, ".")) return TK_DOTS; /* ... */ else return TK_CONCAT; /* .. */ } else if (!isdigit(ls->current)) return '.'; else { read_numeral(ls, seminfo); return TK_NUMBER; } } case EOZ: { return TK_EOS; } default: { if (isspace(ls->current)) { lua_assert(!currIsNewline(ls)); next(ls); continue; } else if (isdigit(ls->current)) { read_numeral(ls, seminfo); return TK_NUMBER; } else if (isalpha(ls->current) || ls->current == '_') { /* identifier or reserved word */ TString *ts; do { save_and_next(ls); } while (isalnum(ls->current) || ls->current == '_'); ts = luaX_newstring(ls, luaZ_buffer(ls->buff), luaZ_bufflen(ls->buff)); if (ts->tsv.reserved > 0) /* reserved word? */ return ts->tsv.reserved - 1 + FIRST_RESERVED; else { seminfo->ts = ts; return TK_NAME; } } else { int c = ls->current; next(ls); return c; /* single-char tokens (+ - / ...) */ } } } } } void luaX_next (LexState *ls) { ls->lastline = ls->linenumber; if (ls->lookahead.token != TK_EOS) { /* is there a look-ahead token? */ ls->t = ls->lookahead; /* use this one */ ls->lookahead.token = TK_EOS; /* and discharge it */ } else ls->t.token = llex(ls, &ls->t.seminfo); /* read next token */ } void luaX_lookahead (LexState *ls) { lua_assert(ls->lookahead.token == TK_EOS); ls->lookahead.token = llex(ls, &ls->lookahead.seminfo); } ================================================ FILE: src/llex.h ================================================ /* ** $Id: llex.h,v 1.58.1.1 2007/12/27 13:02:25 roberto Exp $ ** Lexical Analyzer ** See Copyright Notice in lua.h */ #ifndef llex_h #define llex_h #include "lobject.h" #include "lzio.h" #define FIRST_RESERVED 257 /* maximum length of a reserved word */ #define TOKEN_LEN (sizeof("function")/sizeof(char)) /* * WARNING: if you change the order of this enumeration, * grep "ORDER RESERVED" */ enum RESERVED { /* terminal symbols denoted by reserved words */ TK_AND = FIRST_RESERVED, TK_BREAK, TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT, TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE, /* other terminal symbols */ TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, TK_NUMBER, TK_NAME, TK_STRING, TK_EOS }; /* number of reserved words */ #define NUM_RESERVED (cast(int, TK_WHILE-FIRST_RESERVED+1)) /* array with token `names' */ LUAI_DATA const char *const luaX_tokens []; typedef union { lua_Number r; TString *ts; } SemInfo; /* semantics information */ typedef struct Token { int token; SemInfo seminfo; } Token; typedef struct LexState { int current; /* current character (charint) */ int linenumber; /* input line counter */ int lastline; /* line of last token `consumed' */ Token t; /* current token */ Token lookahead; /* look ahead token */ struct FuncState *fs; /* `FuncState' is private to the parser */ struct lua_State *L; ZIO *z; /* input stream */ Mbuffer *buff; /* buffer for tokens */ TString *source; /* current source name */ char decpoint; /* locale decimal point */ } LexState; LUAI_FUNC void luaX_init (lua_State *L); LUAI_FUNC void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source); LUAI_FUNC TString *luaX_newstring (LexState *ls, const char *str, size_t l); LUAI_FUNC void luaX_next (LexState *ls); LUAI_FUNC void luaX_lookahead (LexState *ls); LUAI_FUNC void luaX_lexerror (LexState *ls, const char *msg, int token); LUAI_FUNC void luaX_syntaxerror (LexState *ls, const char *s); LUAI_FUNC const char *luaX_token2str (LexState *ls, int token); #endif ================================================ FILE: src/llimits.h ================================================ /* ** $Id: llimits.h,v 1.69.1.1 2007/12/27 13:02:25 roberto Exp $ ** Limits, basic types, and some other `installation-dependent' definitions ** See Copyright Notice in lua.h */ #ifndef llimits_h #define llimits_h #include #include #include "lua.h" typedef LUAI_UINT32 lu_int32; typedef LUAI_UMEM lu_mem; typedef LUAI_MEM l_mem; /* chars used as small naturals (so that `char' is reserved for characters) */ typedef unsigned char lu_byte; #define MAX_SIZET ((size_t)(~(size_t)0)-2) #define MAX_LUMEM ((lu_mem)(~(lu_mem)0)-2) #define MAX_INT (INT_MAX-2) /* maximum value of an int (-2 for safety) */ /* ** conversion of pointer to integer ** this is for hashing only; there is no problem if the integer ** cannot hold the whole pointer value */ #define IntPoint(p) ((unsigned int)(lu_mem)(p)) /* type to ensure maximum alignment */ typedef LUAI_USER_ALIGNMENT_T L_Umaxalign; /* result of a `usual argument conversion' over lua_Number */ typedef LUAI_UACNUMBER l_uacNumber; /* internal assertions for in-house debugging */ #ifdef lua_assert #define check_exp(c,e) (lua_assert(c), (e)) #define api_check(l,e) lua_assert(e) #else #define lua_assert(c) ((void)0) #define check_exp(c,e) (e) #define api_check luai_apicheck #endif #ifndef UNUSED #define UNUSED(x) ((void)(x)) /* to avoid warnings */ #endif #ifndef cast #define cast(t, exp) ((t)(exp)) #endif #define cast_byte(i) cast(lu_byte, (i)) #define cast_num(i) cast(lua_Number, (i)) #define cast_int(i) cast(int, (i)) /* ** type for virtual-machine instructions ** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h) */ typedef lu_int32 Instruction; /* maximum stack for a Lua function */ #define MAXSTACK 250 /* minimum size for the string table (must be power of 2) */ #ifndef MINSTRTABSIZE #define MINSTRTABSIZE 32 #endif /* minimum size for string buffer */ #ifndef LUA_MINBUFFER #define LUA_MINBUFFER 32 #endif #ifndef lua_lock #define lua_lock(L) ((void) 0) #define lua_unlock(L) ((void) 0) #endif #ifndef luai_threadyield #define luai_threadyield(L) {lua_unlock(L); lua_lock(L);} #endif /* ** macro to control inclusion of some hard tests on stack reallocation */ #ifndef HARDSTACKTESTS #define condhardstacktests(x) ((void)0) #else #define condhardstacktests(x) x #endif #endif ================================================ FILE: src/lmathlib.c ================================================ /* ** $Id: lmathlib.c,v 1.67.1.1 2007/12/27 13:02:25 roberto Exp $ ** Standard mathematical library ** See Copyright Notice in lua.h */ #include #include #define lmathlib_c #define LUA_LIB #include "lua.h" #include "lauxlib.h" #include "lualib.h" #undef PI #define PI (3.14159265358979323846) #define RADIANS_PER_DEGREE (PI/180.0) static int math_abs (lua_State *L) { lua_pushnumber(L, fabs(luaL_checknumber(L, 1))); return 1; } static int math_sin (lua_State *L) { lua_pushnumber(L, sin(luaL_checknumber(L, 1))); return 1; } static int math_sinh (lua_State *L) { lua_pushnumber(L, sinh(luaL_checknumber(L, 1))); return 1; } static int math_cos (lua_State *L) { lua_pushnumber(L, cos(luaL_checknumber(L, 1))); return 1; } static int math_cosh (lua_State *L) { lua_pushnumber(L, cosh(luaL_checknumber(L, 1))); return 1; } static int math_tan (lua_State *L) { lua_pushnumber(L, tan(luaL_checknumber(L, 1))); return 1; } static int math_tanh (lua_State *L) { lua_pushnumber(L, tanh(luaL_checknumber(L, 1))); return 1; } static int math_asin (lua_State *L) { lua_pushnumber(L, asin(luaL_checknumber(L, 1))); return 1; } static int math_acos (lua_State *L) { lua_pushnumber(L, acos(luaL_checknumber(L, 1))); return 1; } static int math_atan (lua_State *L) { lua_pushnumber(L, atan(luaL_checknumber(L, 1))); return 1; } static int math_atan2 (lua_State *L) { lua_pushnumber(L, atan2(luaL_checknumber(L, 1), luaL_checknumber(L, 2))); return 1; } static int math_ceil (lua_State *L) { lua_pushnumber(L, ceil(luaL_checknumber(L, 1))); return 1; } static int math_floor (lua_State *L) { lua_pushnumber(L, floor(luaL_checknumber(L, 1))); return 1; } static int math_fmod (lua_State *L) { lua_pushnumber(L, fmod(luaL_checknumber(L, 1), luaL_checknumber(L, 2))); return 1; } static int math_modf (lua_State *L) { double ip; double fp = modf(luaL_checknumber(L, 1), &ip); lua_pushnumber(L, ip); lua_pushnumber(L, fp); return 2; } static int math_sqrt (lua_State *L) { lua_pushnumber(L, sqrt(luaL_checknumber(L, 1))); return 1; } static int math_pow (lua_State *L) { lua_pushnumber(L, pow(luaL_checknumber(L, 1), luaL_checknumber(L, 2))); return 1; } static int math_log (lua_State *L) { lua_pushnumber(L, log(luaL_checknumber(L, 1))); return 1; } static int math_log10 (lua_State *L) { lua_pushnumber(L, log10(luaL_checknumber(L, 1))); return 1; } static int math_exp (lua_State *L) { lua_pushnumber(L, exp(luaL_checknumber(L, 1))); return 1; } static int math_deg (lua_State *L) { lua_pushnumber(L, luaL_checknumber(L, 1)/RADIANS_PER_DEGREE); return 1; } static int math_rad (lua_State *L) { lua_pushnumber(L, luaL_checknumber(L, 1)*RADIANS_PER_DEGREE); return 1; } static int math_frexp (lua_State *L) { int e; lua_pushnumber(L, frexp(luaL_checknumber(L, 1), &e)); lua_pushinteger(L, e); return 2; } static int math_ldexp (lua_State *L) { lua_pushnumber(L, ldexp(luaL_checknumber(L, 1), luaL_checkint(L, 2))); return 1; } static int math_min (lua_State *L) { int n = lua_gettop(L); /* number of arguments */ lua_Number dmin = luaL_checknumber(L, 1); int i; for (i=2; i<=n; i++) { lua_Number d = luaL_checknumber(L, i); if (d < dmin) dmin = d; } lua_pushnumber(L, dmin); return 1; } static int math_max (lua_State *L) { int n = lua_gettop(L); /* number of arguments */ lua_Number dmax = luaL_checknumber(L, 1); int i; for (i=2; i<=n; i++) { lua_Number d = luaL_checknumber(L, i); if (d > dmax) dmax = d; } lua_pushnumber(L, dmax); return 1; } static int math_random (lua_State *L) { /* the `%' avoids the (rare) case of r==1, and is needed also because on some systems (SunOS!) `rand()' may return a value larger than RAND_MAX */ lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX; switch (lua_gettop(L)) { /* check number of arguments */ case 0: { /* no arguments */ lua_pushnumber(L, r); /* Number between 0 and 1 */ break; } case 1: { /* only upper limit */ int u = luaL_checkint(L, 1); luaL_argcheck(L, 1<=u, 1, "interval is empty"); lua_pushnumber(L, floor(r*u)+1); /* int between 1 and `u' */ break; } case 2: { /* lower and upper limits */ int l = luaL_checkint(L, 1); int u = luaL_checkint(L, 2); luaL_argcheck(L, l<=u, 2, "interval is empty"); lua_pushnumber(L, floor(r*(u-l+1))+l); /* int between `l' and `u' */ break; } default: return luaL_error(L, "wrong number of arguments"); } return 1; } static int math_randomseed (lua_State *L) { srand(luaL_checkint(L, 1)); return 0; } static const luaL_Reg mathlib[] = { {"abs", math_abs}, {"acos", math_acos}, {"asin", math_asin}, {"atan2", math_atan2}, {"atan", math_atan}, {"ceil", math_ceil}, {"cosh", math_cosh}, {"cos", math_cos}, {"deg", math_deg}, {"exp", math_exp}, {"floor", math_floor}, {"fmod", math_fmod}, {"frexp", math_frexp}, {"ldexp", math_ldexp}, {"log10", math_log10}, {"log", math_log}, {"max", math_max}, {"min", math_min}, {"modf", math_modf}, {"pow", math_pow}, {"rad", math_rad}, {"random", math_random}, {"randomseed", math_randomseed}, {"sinh", math_sinh}, {"sin", math_sin}, {"sqrt", math_sqrt}, {"tanh", math_tanh}, {"tan", math_tan}, {NULL, NULL} }; /* ** Open math library */ LUALIB_API int luaopen_math (lua_State *L) { luaL_register(L, LUA_MATHLIBNAME, mathlib); lua_pushnumber(L, PI); lua_setfield(L, -2, "pi"); lua_pushnumber(L, HUGE_VAL); lua_setfield(L, -2, "huge"); #if defined(LUA_COMPAT_MOD) lua_getfield(L, -1, "fmod"); lua_setfield(L, -2, "mod"); #endif return 1; } ================================================ FILE: src/lmem.c ================================================ /* ** $Id: lmem.c,v 1.70.1.1 2007/12/27 13:02:25 roberto Exp $ ** Interface to Memory Manager ** See Copyright Notice in lua.h */ #include #define lmem_c #define LUA_CORE #include "lua.h" #include "ldebug.h" #include "ldo.h" #include "lmem.h" #include "lobject.h" #include "lstate.h" /* ** About the realloc function: ** void * frealloc (void *ud, void *ptr, size_t osize, size_t nsize); ** (`osize' is the old size, `nsize' is the new size) ** ** Lua ensures that (ptr == NULL) iff (osize == 0). ** ** * frealloc(ud, NULL, 0, x) creates a new block of size `x' ** ** * frealloc(ud, p, x, 0) frees the block `p' ** (in this specific case, frealloc must return NULL). ** particularly, frealloc(ud, NULL, 0, 0) does nothing ** (which is equivalent to free(NULL) in ANSI C) ** ** frealloc returns NULL if it cannot create or reallocate the area ** (any reallocation to an equal or smaller size cannot fail!) */ #define MINSIZEARRAY 4 void *luaM_growaux_ (lua_State *L, void *block, int *size, size_t size_elems, int limit, const char *errormsg) { void *newblock; int newsize; if (*size >= limit/2) { /* cannot double it? */ if (*size >= limit) /* cannot grow even a little? */ luaG_runerror(L, errormsg); newsize = limit; /* still have at least one free place */ } else { newsize = (*size)*2; if (newsize < MINSIZEARRAY) newsize = MINSIZEARRAY; /* minimum size */ } newblock = luaM_reallocv(L, block, *size, newsize, size_elems); *size = newsize; /* update only when everything else is OK */ return newblock; } void *luaM_toobig (lua_State *L) { luaG_runerror(L, "memory allocation error: block too big"); return NULL; /* to avoid warnings */ } /* ** generic allocation routine. */ void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) { global_State *g = G(L); lua_assert((osize == 0) == (block == NULL)); block = (*g->frealloc)(g->ud, block, osize, nsize); if (block == NULL && nsize > 0) luaD_throw(L, LUA_ERRMEM); lua_assert((nsize == 0) == (block == NULL)); g->totalbytes = (g->totalbytes - osize) + nsize; return block; } ================================================ FILE: src/lmem.h ================================================ /* ** $Id: lmem.h,v 1.31.1.1 2007/12/27 13:02:25 roberto Exp $ ** Interface to Memory Manager ** See Copyright Notice in lua.h */ #ifndef lmem_h #define lmem_h #include #include "llimits.h" #include "lua.h" #define MEMERRMSG "not enough memory" #define luaM_reallocv(L,b,on,n,e) \ ((cast(size_t, (n)+1) <= MAX_SIZET/(e)) ? /* +1 to avoid warnings */ \ luaM_realloc_(L, (b), (on)*(e), (n)*(e)) : \ luaM_toobig(L)) #define luaM_freemem(L, b, s) luaM_realloc_(L, (b), (s), 0) #define luaM_free(L, b) luaM_realloc_(L, (b), sizeof(*(b)), 0) #define luaM_freearray(L, b, n, t) luaM_reallocv(L, (b), n, 0, sizeof(t)) #define luaM_malloc(L,t) luaM_realloc_(L, NULL, 0, (t)) #define luaM_new(L,t) cast(t *, luaM_malloc(L, sizeof(t))) #define luaM_newvector(L,n,t) \ cast(t *, luaM_reallocv(L, NULL, 0, n, sizeof(t))) #define luaM_growvector(L,v,nelems,size,t,limit,e) \ if ((nelems)+1 > (size)) \ ((v)=cast(t *, luaM_growaux_(L,v,&(size),sizeof(t),limit,e))) #define luaM_reallocvector(L, v,oldn,n,t) \ ((v)=cast(t *, luaM_reallocv(L, v, oldn, n, sizeof(t)))) LUAI_FUNC void *luaM_realloc_ (lua_State *L, void *block, size_t oldsize, size_t size); LUAI_FUNC void *luaM_toobig (lua_State *L); LUAI_FUNC void *luaM_growaux_ (lua_State *L, void *block, int *size, size_t size_elem, int limit, const char *errormsg); #endif ================================================ FILE: src/lobject.c ================================================ /* ** $Id: lobject.c,v 2.22.1.1 2007/12/27 13:02:25 roberto Exp $ ** Some generic functions over Lua objects ** See Copyright Notice in lua.h */ #include #include #include #include #include #define lobject_c #define LUA_CORE #include "lua.h" #include "ldo.h" #include "lmem.h" #include "lobject.h" #include "lstate.h" #include "lstring.h" #include "lvm.h" const TValue luaO_nilobject_ = {{NULL}, LUA_TNIL}; /* ** converts an integer to a "floating point byte", represented as ** (eeeeexxx), where the real value is (1xxx) * 2^(eeeee - 1) if ** eeeee != 0 and (xxx) otherwise. */ int luaO_int2fb (unsigned int x) { int e = 0; /* expoent */ while (x >= 16) { x = (x+1) >> 1; e++; } if (x < 8) return x; else return ((e+1) << 3) | (cast_int(x) - 8); } /* converts back */ int luaO_fb2int (int x) { int e = (x >> 3) & 31; if (e == 0) return x; else return ((x & 7)+8) << (e - 1); } int luaO_log2 (unsigned int x) { static const lu_byte log_2[256] = { 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 }; int l = -1; while (x >= 256) { l += 8; x >>= 8; } return l + log_2[x]; } int luaO_rawequalObj (const TValue *t1, const TValue *t2) { if (ttype(t1) != ttype(t2)) return 0; else switch (ttype(t1)) { case LUA_TNIL: return 1; case LUA_TNUMBER: return luai_numeq(nvalue(t1), nvalue(t2)); case LUA_TBOOLEAN: return bvalue(t1) == bvalue(t2); /* boolean true must be 1 !! */ case LUA_TLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); default: lua_assert(iscollectable(t1)); return gcvalue(t1) == gcvalue(t2); } } int luaO_str2d (const char *s, lua_Number *result) { char *endptr; *result = lua_str2number(s, &endptr); if (endptr == s) return 0; /* conversion failed */ if (*endptr == 'x' || *endptr == 'X') /* maybe an hexadecimal constant? */ *result = cast_num(strtoul(s, &endptr, 16)); if (*endptr == '\0') return 1; /* most common case */ while (isspace(cast(unsigned char, *endptr))) endptr++; if (*endptr != '\0') return 0; /* invalid trailing characters? */ return 1; } static void pushstr (lua_State *L, const char *str) { setsvalue2s(L, L->top, luaS_new(L, str)); incr_top(L); } /* this function handles only `%d', `%c', %f, %p, and `%s' formats */ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { int n = 1; pushstr(L, ""); for (;;) { const char *e = strchr(fmt, '%'); if (e == NULL) break; setsvalue2s(L, L->top, luaS_newlstr(L, fmt, e-fmt)); incr_top(L); switch (*(e+1)) { case 's': { const char *s = va_arg(argp, char *); if (s == NULL) s = "(null)"; pushstr(L, s); break; } case 'c': { char buff[2]; buff[0] = cast(char, va_arg(argp, int)); buff[1] = '\0'; pushstr(L, buff); break; } case 'd': { setnvalue(L->top, cast_num(va_arg(argp, int))); incr_top(L); break; } case 'f': { setnvalue(L->top, cast_num(va_arg(argp, l_uacNumber))); incr_top(L); break; } case 'p': { char buff[4*sizeof(void *) + 8]; /* should be enough space for a `%p' */ sprintf(buff, "%p", va_arg(argp, void *)); pushstr(L, buff); break; } case '%': { pushstr(L, "%"); break; } default: { char buff[3]; buff[0] = '%'; buff[1] = *(e+1); buff[2] = '\0'; pushstr(L, buff); break; } } n += 2; fmt = e+2; } pushstr(L, fmt); luaV_concat(L, n+1, cast_int(L->top - L->base) - 1); L->top -= n; return svalue(L->top - 1); } const char *luaO_pushfstring (lua_State *L, const char *fmt, ...) { const char *msg; va_list argp; va_start(argp, fmt); msg = luaO_pushvfstring(L, fmt, argp); va_end(argp); return msg; } void luaO_chunkid (char *out, const char *source, size_t bufflen) { if (*source == '=') { strncpy(out, source+1, bufflen); /* remove first char */ out[bufflen-1] = '\0'; /* ensures null termination */ } else { /* out = "source", or "...source" */ if (*source == '@') { size_t l; source++; /* skip the `@' */ bufflen -= sizeof(" '...' "); l = strlen(source); strcpy(out, ""); if (l > bufflen) { source += (l-bufflen); /* get last part of file name */ strcat(out, "..."); } strcat(out, source); } else { /* out = [string "string"] */ size_t len = strcspn(source, "\n\r"); /* stop at first newline */ bufflen -= sizeof(" [string \"...\"] "); if (len > bufflen) len = bufflen; strcpy(out, "[string \""); if (source[len] != '\0') { /* must truncate? */ strncat(out, source, len); strcat(out, "..."); } else strcat(out, source); strcat(out, "\"]"); } } } ================================================ FILE: src/lobject.h ================================================ /* ** $Id: lobject.h,v 2.20.1.2 2008/08/06 13:29:48 roberto Exp $ ** Type definitions for Lua objects ** See Copyright Notice in lua.h */ #ifndef lobject_h #define lobject_h #include #include "llimits.h" #include "lua.h" /* tags for values visible from Lua */ #define LAST_TAG LUA_TTHREAD #define NUM_TAGS (LAST_TAG+1) /* ** Extra tags for non-values */ #define LUA_TPROTO (LAST_TAG+1) #define LUA_TUPVAL (LAST_TAG+2) #define LUA_TDEADKEY (LAST_TAG+3) /* ** Union of all collectable objects */ typedef union GCObject GCObject; /* ** Common Header for all collectable objects (in macro form, to be ** included in other objects) */ #define CommonHeader GCObject *next; lu_byte tt; lu_byte marked /* ** Common header in struct form */ typedef struct GCheader { CommonHeader; } GCheader; /* ** Union of all Lua values */ typedef union { GCObject *gc; void *p; lua_Number n; int b; } Value; /* ** Tagged Values */ #define TValuefields Value value; int tt typedef struct lua_TValue { TValuefields; } TValue; /* Macros to test type */ #define ttisnil(o) (ttype(o) == LUA_TNIL) #define ttisnumber(o) (ttype(o) == LUA_TNUMBER) #define ttisstring(o) (ttype(o) == LUA_TSTRING) #define ttistable(o) (ttype(o) == LUA_TTABLE) #define ttisfunction(o) (ttype(o) == LUA_TFUNCTION) #define ttisboolean(o) (ttype(o) == LUA_TBOOLEAN) #define ttisuserdata(o) (ttype(o) == LUA_TUSERDATA) #define ttisthread(o) (ttype(o) == LUA_TTHREAD) #define ttislightuserdata(o) (ttype(o) == LUA_TLIGHTUSERDATA) /* Macros to access values */ #define ttype(o) ((o)->tt) #define gcvalue(o) check_exp(iscollectable(o), (o)->value.gc) #define pvalue(o) check_exp(ttislightuserdata(o), (o)->value.p) #define nvalue(o) check_exp(ttisnumber(o), (o)->value.n) #define rawtsvalue(o) check_exp(ttisstring(o), &(o)->value.gc->ts) #define tsvalue(o) (&rawtsvalue(o)->tsv) #define rawuvalue(o) check_exp(ttisuserdata(o), &(o)->value.gc->u) #define uvalue(o) (&rawuvalue(o)->uv) #define clvalue(o) check_exp(ttisfunction(o), &(o)->value.gc->cl) #define hvalue(o) check_exp(ttistable(o), &(o)->value.gc->h) #define bvalue(o) check_exp(ttisboolean(o), (o)->value.b) #define thvalue(o) check_exp(ttisthread(o), &(o)->value.gc->th) #define l_isfalse(o) (ttisnil(o) || (ttisboolean(o) && bvalue(o) == 0)) /* ** for internal debug only */ #define checkconsistency(obj) \ lua_assert(!iscollectable(obj) || (ttype(obj) == (obj)->value.gc->gch.tt)) #define checkliveness(g,obj) \ lua_assert(!iscollectable(obj) || \ ((ttype(obj) == (obj)->value.gc->gch.tt) && !isdead(g, (obj)->value.gc))) /* Macros to set values */ #define setnilvalue(obj) ((obj)->tt=LUA_TNIL) #define setnvalue(obj,x) \ { TValue *i_o=(obj); i_o->value.n=(x); i_o->tt=LUA_TNUMBER; } #define setpvalue(obj,x) \ { TValue *i_o=(obj); i_o->value.p=(x); i_o->tt=LUA_TLIGHTUSERDATA; } #define setbvalue(obj,x) \ { TValue *i_o=(obj); i_o->value.b=(x); i_o->tt=LUA_TBOOLEAN; } #define setsvalue(L,obj,x) \ { TValue *i_o=(obj); \ i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TSTRING; \ checkliveness(G(L),i_o); } #define setuvalue(L,obj,x) \ { TValue *i_o=(obj); \ i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TUSERDATA; \ checkliveness(G(L),i_o); } #define setthvalue(L,obj,x) \ { TValue *i_o=(obj); \ i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TTHREAD; \ checkliveness(G(L),i_o); } #define setclvalue(L,obj,x) \ { TValue *i_o=(obj); \ i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TFUNCTION; \ checkliveness(G(L),i_o); } #define sethvalue(L,obj,x) \ { TValue *i_o=(obj); \ i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TTABLE; \ checkliveness(G(L),i_o); } #define setptvalue(L,obj,x) \ { TValue *i_o=(obj); \ i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TPROTO; \ checkliveness(G(L),i_o); } #define setobj(L,obj1,obj2) \ { const TValue *o2=(obj2); TValue *o1=(obj1); \ o1->value = o2->value; o1->tt=o2->tt; \ checkliveness(G(L),o1); } /* ** different types of sets, according to destination */ /* from stack to (same) stack */ #define setobjs2s setobj /* to stack (not from same stack) */ #define setobj2s setobj #define setsvalue2s setsvalue #define sethvalue2s sethvalue #define setptvalue2s setptvalue /* from table to same table */ #define setobjt2t setobj /* to table */ #define setobj2t setobj /* to new object */ #define setobj2n setobj #define setsvalue2n setsvalue #define setttype(obj, tt) (ttype(obj) = (tt)) #define iscollectable(o) (ttype(o) >= LUA_TSTRING) typedef TValue *StkId; /* index to stack elements */ /* ** String headers for string table */ typedef union TString { L_Umaxalign dummy; /* ensures maximum alignment for strings */ struct { CommonHeader; lu_byte reserved; unsigned int hash; size_t len; } tsv; } TString; #define getstr(ts) cast(const char *, (ts) + 1) #define svalue(o) getstr(rawtsvalue(o)) typedef union Udata { L_Umaxalign dummy; /* ensures maximum alignment for `local' udata */ struct { CommonHeader; struct Table *metatable; struct Table *env; size_t len; } uv; } Udata; /* ** Function Prototypes */ typedef struct Proto { CommonHeader; TValue *k; /* constants used by the function */ Instruction *code; struct Proto **p; /* functions defined inside the function */ int *lineinfo; /* map from opcodes to source lines */ struct LocVar *locvars; /* information about local variables */ TString **upvalues; /* upvalue names */ TString *source; int sizeupvalues; int sizek; /* size of `k' */ int sizecode; int sizelineinfo; int sizep; /* size of `p' */ int sizelocvars; int linedefined; int lastlinedefined; GCObject *gclist; lu_byte nups; /* number of upvalues */ lu_byte numparams; lu_byte is_vararg; lu_byte maxstacksize; } Proto; /* masks for new-style vararg */ #define VARARG_HASARG 1 #define VARARG_ISVARARG 2 #define VARARG_NEEDSARG 4 typedef struct LocVar { TString *varname; int startpc; /* first point where variable is active */ int endpc; /* first point where variable is dead */ } LocVar; /* ** Upvalues */ typedef struct UpVal { CommonHeader; TValue *v; /* points to stack or to its own value */ union { TValue value; /* the value (when closed) */ struct { /* double linked list (when open) */ struct UpVal *prev; struct UpVal *next; } l; } u; } UpVal; /* ** Closures */ #define ClosureHeader \ CommonHeader; lu_byte isC; lu_byte nupvalues; GCObject *gclist; \ struct Table *env typedef struct CClosure { ClosureHeader; lua_CFunction f; TValue upvalue[1]; } CClosure; typedef struct LClosure { ClosureHeader; struct Proto *p; UpVal *upvals[1]; } LClosure; typedef union Closure { CClosure c; LClosure l; } Closure; #define iscfunction(o) (ttype(o) == LUA_TFUNCTION && clvalue(o)->c.isC) #define isLfunction(o) (ttype(o) == LUA_TFUNCTION && !clvalue(o)->c.isC) /* ** Tables */ typedef union TKey { struct { TValuefields; struct Node *next; /* for chaining */ } nk; TValue tvk; } TKey; typedef struct Node { TValue i_val; TKey i_key; } Node; typedef struct Table { CommonHeader; lu_byte flags; /* 1<

    lsizenode)) #define luaO_nilobject (&luaO_nilobject_) LUAI_DATA const TValue luaO_nilobject_; #define ceillog2(x) (luaO_log2((x)-1) + 1) LUAI_FUNC int luaO_log2 (unsigned int x); LUAI_FUNC int luaO_int2fb (unsigned int x); LUAI_FUNC int luaO_fb2int (int x); LUAI_FUNC int luaO_rawequalObj (const TValue *t1, const TValue *t2); LUAI_FUNC int luaO_str2d (const char *s, lua_Number *result); LUAI_FUNC const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp); LUAI_FUNC const char *luaO_pushfstring (lua_State *L, const char *fmt, ...); LUAI_FUNC void luaO_chunkid (char *out, const char *source, size_t len); #endif ================================================ FILE: src/lopcodes.c ================================================ /* ** $Id: lopcodes.c,v 1.37.1.1 2007/12/27 13:02:25 roberto Exp $ ** See Copyright Notice in lua.h */ #define lopcodes_c #define LUA_CORE #include "lopcodes.h" /* ORDER OP */ const char *const luaP_opnames[NUM_OPCODES+1] = { "MOVE", "LOADK", "LOADBOOL", "LOADNIL", "GETUPVAL", "GETGLOBAL", "GETTABLE", "SETGLOBAL", "SETUPVAL", "SETTABLE", "NEWTABLE", "SELF", "ADD", "SUB", "MUL", "DIV", "MOD", "POW", "UNM", "NOT", "LEN", "CONCAT", "JMP", "EQ", "LT", "LE", "TEST", "TESTSET", "CALL", "TAILCALL", "RETURN", "FORLOOP", "FORPREP", "TFORLOOP", "SETLIST", "CLOSE", "CLOSURE", "VARARG", NULL }; #define opmode(t,a,b,c,m) (((t)<<7) | ((a)<<6) | ((b)<<4) | ((c)<<2) | (m)) const lu_byte luaP_opmodes[NUM_OPCODES] = { /* T A B C mode opcode */ opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_MOVE */ ,opmode(0, 1, OpArgK, OpArgN, iABx) /* OP_LOADK */ ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_LOADBOOL */ ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_LOADNIL */ ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_GETUPVAL */ ,opmode(0, 1, OpArgK, OpArgN, iABx) /* OP_GETGLOBAL */ ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_GETTABLE */ ,opmode(0, 0, OpArgK, OpArgN, iABx) /* OP_SETGLOBAL */ ,opmode(0, 0, OpArgU, OpArgN, iABC) /* OP_SETUPVAL */ ,opmode(0, 0, OpArgK, OpArgK, iABC) /* OP_SETTABLE */ ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_NEWTABLE */ ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_SELF */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_ADD */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_SUB */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MUL */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_DIV */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MOD */ ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_POW */ ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_UNM */ ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_NOT */ ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_LEN */ ,opmode(0, 1, OpArgR, OpArgR, iABC) /* OP_CONCAT */ ,opmode(0, 0, OpArgR, OpArgN, iAsBx) /* OP_JMP */ ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_EQ */ ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_LT */ ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_LE */ ,opmode(1, 1, OpArgR, OpArgU, iABC) /* OP_TEST */ ,opmode(1, 1, OpArgR, OpArgU, iABC) /* OP_TESTSET */ ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_CALL */ ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_TAILCALL */ ,opmode(0, 0, OpArgU, OpArgN, iABC) /* OP_RETURN */ ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_FORLOOP */ ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_FORPREP */ ,opmode(1, 0, OpArgN, OpArgU, iABC) /* OP_TFORLOOP */ ,opmode(0, 0, OpArgU, OpArgU, iABC) /* OP_SETLIST */ ,opmode(0, 0, OpArgN, OpArgN, iABC) /* OP_CLOSE */ ,opmode(0, 1, OpArgU, OpArgN, iABx) /* OP_CLOSURE */ ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_VARARG */ }; ================================================ FILE: src/lopcodes.h ================================================ /* ** $Id: lopcodes.h,v 1.125.1.1 2007/12/27 13:02:25 roberto Exp $ ** Opcodes for Lua virtual machine ** See Copyright Notice in lua.h */ #ifndef lopcodes_h #define lopcodes_h #include "llimits.h" /*=========================================================================== 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. ===========================================================================*/ enum OpMode {iABC, iABx, iAsBx}; /* basic instruction format */ /* ** size and position of opcode arguments. */ #define SIZE_C 9 #define SIZE_B 9 #define SIZE_Bx (SIZE_C + SIZE_B) #define SIZE_A 8 #define SIZE_OP 6 #define POS_OP 0 #define POS_A (POS_OP + SIZE_OP) #define POS_C (POS_A + SIZE_A) #define POS_B (POS_C + SIZE_C) #define POS_Bx POS_C /* ** limits for opcode arguments. ** we use (signed) int to manipulate most arguments, ** so they must fit in LUAI_BITSINT-1 bits (-1 for sign) */ #if SIZE_Bx < LUAI_BITSINT-1 #define MAXARG_Bx ((1<>1) /* `sBx' is signed */ #else #define MAXARG_Bx MAX_INT #define MAXARG_sBx MAX_INT #endif #define MAXARG_A ((1<>POS_OP) & MASK1(SIZE_OP,0))) #define SET_OPCODE(i,o) ((i) = (((i)&MASK0(SIZE_OP,POS_OP)) | \ ((cast(Instruction, o)<>POS_A) & MASK1(SIZE_A,0))) #define SETARG_A(i,u) ((i) = (((i)&MASK0(SIZE_A,POS_A)) | \ ((cast(Instruction, u)<>POS_B) & MASK1(SIZE_B,0))) #define SETARG_B(i,b) ((i) = (((i)&MASK0(SIZE_B,POS_B)) | \ ((cast(Instruction, b)<>POS_C) & MASK1(SIZE_C,0))) #define SETARG_C(i,b) ((i) = (((i)&MASK0(SIZE_C,POS_C)) | \ ((cast(Instruction, b)<>POS_Bx) & MASK1(SIZE_Bx,0))) #define SETARG_Bx(i,b) ((i) = (((i)&MASK0(SIZE_Bx,POS_Bx)) | \ ((cast(Instruction, b)< C) then pc++ */ OP_TESTSET,/* 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)*/ OP_CLOSURE,/* A Bx R(A) := closure(KPROTO[Bx], R(A), ... ,R(A+n)) */ OP_VARARG/* A B R(A), R(A+1), ..., R(A+B-1) = vararg */ } OpCode; #define NUM_OPCODES (cast(int, OP_VARARG) + 1) /*=========================================================================== Notes: (*) 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'. (*) In OP_VARARG, if (B == 0) then use actual number of varargs and set top (like in OP_CALL with C == 0). (*) In OP_RETURN, if (B == 0) then return up to `top' (*) In OP_SETLIST, if (B == 0) then B = `top'; if (C == 0) then next `instruction' is real C (*) For comparisons, A specifies what condition the test should accept (true or false). (*) All `skips' (pc++) assume that next instruction is a jump ===========================================================================*/ /* ** masks for instruction properties. The format is: ** bits 0-1: op mode ** bits 2-3: C arg mode ** bits 4-5: B arg mode ** bit 6: instruction set register A ** bit 7: operator is a test */ enum OpArgMask { OpArgN, /* argument is not used */ OpArgU, /* argument is used */ OpArgR, /* argument is a register or a jump offset */ OpArgK /* argument is a constant or register/constant */ }; LUAI_DATA const lu_byte luaP_opmodes[NUM_OPCODES]; #define getOpMode(m) (cast(enum OpMode, luaP_opmodes[m] & 3)) #define getBMode(m) (cast(enum OpArgMask, (luaP_opmodes[m] >> 4) & 3)) #define getCMode(m) (cast(enum OpArgMask, (luaP_opmodes[m] >> 2) & 3)) #define testAMode(m) (luaP_opmodes[m] & (1 << 6)) #define testTMode(m) (luaP_opmodes[m] & (1 << 7)) LUAI_DATA const char *const luaP_opnames[NUM_OPCODES+1]; /* opcode names */ /* number of list items to accumulate before a SETLIST instruction */ #define LFIELDS_PER_FLUSH 50 #endif ================================================ FILE: src/loslib.c ================================================ /* ** $Id: loslib.c,v 1.19.1.3 2008/01/18 16:38:18 roberto Exp $ ** Standard Operating System library ** See Copyright Notice in lua.h */ #include #include #include #include #include #define loslib_c #define LUA_LIB #include "lua.h" #include "lauxlib.h" #include "lualib.h" #include "teliva.h" static int os_pushresult (lua_State *L, int i, const char *filename) { int en = errno; /* calls to Lua API may change this value */ if (i) { lua_pushboolean(L, 1); return 1; } else { lua_pushnil(L); lua_pushfstring(L, "%s: %s", filename, strerror(en)); lua_pushinteger(L, en); return 3; } } static char oslib_errbuf[1024] = {0}; static int os_remove (lua_State *L) { const char *filename = luaL_checkstring(L, 1); if (starts_with(filename, "teliva_tmp_")) { /* continue */ } else if (starts_with(filename, "teliva_")) { snprintf(oslib_errbuf, 1024, "app tried to remove file '%s'; that's never allowed for filenames starting with 'teliva_'", filename); Previous_message = oslib_errbuf; return os_pushresult(L, 0, filename); } else if (contains(filename, "./")) { snprintf(oslib_errbuf, 1024, "app tried to remove file '%s'; relative paths are never allowed", filename); Previous_message = oslib_errbuf; return os_pushresult(L, 0, filename); } else if (!file_operation_permitted(filename, "w")) { snprintf(oslib_errbuf, 1024, "app tried to remove file '%s'; give it write permissions (ctrl-p) if that is expected", filename); Previous_message = oslib_errbuf; return os_pushresult(L, 0, filename); } return os_pushresult(L, remove(filename) == 0, filename); } static int os_rename (lua_State *L) { const char *fromname = luaL_checkstring(L, 1); const char *toname = luaL_checkstring(L, 2); /* Sandboxing { * A rename is like reading from one file and writing to another file. */ if (starts_with(fromname, "teliva_tmp_")) { /* continue */ } else if (starts_with(fromname, "teliva_")) { snprintf(oslib_errbuf, 1024, "app tried to rename file '%s'; that's never allowed for filenames starting with 'teliva_'", fromname); Previous_message = oslib_errbuf; return os_pushresult(L, 0, fromname); } else if (contains(fromname, "./")) { snprintf(oslib_errbuf, 1024, "app tried to rename file '%s'; relative paths are never allowed", fromname); Previous_message = oslib_errbuf; return os_pushresult(L, 0, fromname); } else if (!file_operation_permitted(fromname, "r")) { snprintf(oslib_errbuf, 1024, "app tried to rename file '%s'; give it read permissions (ctrl-p) if that is expected", fromname); Previous_message = oslib_errbuf; return os_pushresult(L, 0, fromname); } if (starts_with(toname, "teliva_tmp_")) { /* continue */ } else if (starts_with(toname, "teliva_")) { snprintf(oslib_errbuf, 1024, "app tried to rename to file '%s'; that's never allowed for filenames starting with 'teliva_'", toname); Previous_message = oslib_errbuf; return os_pushresult(L, 0, toname); } else if (contains(fromname, "./")) { snprintf(oslib_errbuf, 1024, "app tried to rename to file '%s'; relative paths are never allowed", toname); Previous_message = oslib_errbuf; return os_pushresult(L, 0, toname); } else if (!file_operation_permitted(toname, "w")) { snprintf(oslib_errbuf, 1024, "app tried to rename to file '%s'; give it write permissions (ctrl-p) if that is expected", toname); Previous_message = oslib_errbuf; return os_pushresult(L, 0, toname); } /* } */ return os_pushresult(L, rename(fromname, toname) == 0, fromname); } static int os_tmpname (lua_State *L) { char buff[LUA_TMPNAMBUFSIZE]; int err; lua_tmpnam(buff, err); if (err) return luaL_error(L, "unable to generate a unique filename"); lua_pushstring(L, buff); return 1; } static int os_clock (lua_State *L) { lua_pushnumber(L, ((lua_Number)clock())/(lua_Number)CLOCKS_PER_SEC); return 1; } /* ** {====================================================== ** Time/Date operations ** { year=%Y, month=%m, day=%d, hour=%H, min=%M, sec=%S, ** wday=%w+1, yday=%j, isdst=? } ** ======================================================= */ static void setfield (lua_State *L, const char *key, int value) { lua_pushinteger(L, value); lua_setfield(L, -2, key); } static void setboolfield (lua_State *L, const char *key, int value) { if (value < 0) /* undefined? */ return; /* does not set field */ lua_pushboolean(L, value); lua_setfield(L, -2, key); } static int getboolfield (lua_State *L, const char *key) { int res; lua_getfield(L, -1, key); res = lua_isnil(L, -1) ? -1 : lua_toboolean(L, -1); lua_pop(L, 1); return res; } static int getfield (lua_State *L, const char *key, int d) { int res; lua_getfield(L, -1, key); if (lua_isnumber(L, -1)) res = (int)lua_tointeger(L, -1); else { if (d < 0) return luaL_error(L, "field " LUA_QS " missing in date table", key); res = d; } lua_pop(L, 1); return res; } static int os_date (lua_State *L) { const char *s = luaL_optstring(L, 1, "%c"); time_t t = luaL_opt(L, (time_t)luaL_checknumber, 2, time(NULL)); struct tm *stm; if (*s == '!') { /* UTC? */ stm = gmtime(&t); s++; /* skip `!' */ } else stm = localtime(&t); if (stm == NULL) /* invalid date? */ lua_pushnil(L); else if (strcmp(s, "*t") == 0) { lua_createtable(L, 0, 9); /* 9 = number of fields */ setfield(L, "sec", stm->tm_sec); setfield(L, "min", stm->tm_min); setfield(L, "hour", stm->tm_hour); setfield(L, "day", stm->tm_mday); setfield(L, "month", stm->tm_mon+1); setfield(L, "year", stm->tm_year+1900); setfield(L, "wday", stm->tm_wday+1); setfield(L, "yday", stm->tm_yday+1); setboolfield(L, "isdst", stm->tm_isdst); } else { char cc[3]; luaL_Buffer b; cc[0] = '%'; cc[2] = '\0'; luaL_buffinit(L, &b); for (; *s; s++) { if (*s != '%' || *(s + 1) == '\0') /* no conversion specifier? */ luaL_addchar(&b, *s); else { size_t reslen; char buff[200]; /* should be big enough for any conversion result */ cc[1] = *(++s); reslen = strftime(buff, sizeof(buff), cc, stm); luaL_addlstring(&b, buff, reslen); } } luaL_pushresult(&b); } return 1; } static int os_time (lua_State *L) { time_t t; if (lua_isnoneornil(L, 1)) /* called without args? */ t = time(NULL); /* get current time */ else { struct tm ts; luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 1); /* make sure table is at the top */ ts.tm_sec = getfield(L, "sec", 0); ts.tm_min = getfield(L, "min", 0); ts.tm_hour = getfield(L, "hour", 12); ts.tm_mday = getfield(L, "day", -1); ts.tm_mon = getfield(L, "month", -1) - 1; ts.tm_year = getfield(L, "year", -1) - 1900; ts.tm_isdst = getboolfield(L, "isdst"); t = mktime(&ts); } if (t == (time_t)(-1)) lua_pushnil(L); else lua_pushnumber(L, (lua_Number)t); return 1; } static int os_difftime (lua_State *L) { lua_pushnumber(L, difftime((time_t)(luaL_checknumber(L, 1)), (time_t)(luaL_optnumber(L, 2, 0)))); return 1; } /* }====================================================== */ static int os_setlocale (lua_State *L) { static const int cat[] = {LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME}; static const char *const catnames[] = {"all", "collate", "ctype", "monetary", "numeric", "time", NULL}; const char *l = luaL_optstring(L, 1, NULL); int op = luaL_checkoption(L, 2, "all", catnames); lua_pushstring(L, setlocale(cat[op], l)); return 1; } static int os_exit (lua_State *L) { exit(luaL_optint(L, 1, EXIT_SUCCESS)); } static const luaL_Reg syslib[] = { {"clock", os_clock}, {"date", os_date}, {"difftime", os_difftime}, /* no execute without sandboxing it */ {"exit", os_exit}, /* no getenv without sandboxing it */ {"remove", os_remove}, {"rename", os_rename}, {"setlocale", os_setlocale}, {"time", os_time}, {"tmpname", os_tmpname}, {NULL, NULL} }; /* }====================================================== */ LUALIB_API int luaopen_os (lua_State *L) { luaL_register(L, LUA_OSLIBNAME, syslib); return 1; } ================================================ FILE: src/lparser.c ================================================ /* ** $Id: lparser.c,v 2.42.1.4 2011/10/21 19:31:42 roberto Exp $ ** Lua Parser ** See Copyright Notice in lua.h */ #include #define lparser_c #define LUA_CORE #include "lua.h" #include "lcode.h" #include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "llex.h" #include "lmem.h" #include "lobject.h" #include "lopcodes.h" #include "lparser.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #define hasmultret(k) ((k) == VCALL || (k) == VVARARG) #define getlocvar(fs, i) ((fs)->f->locvars[(fs)->actvar[i]]) #define luaY_checklimit(fs,v,l,m) if ((v)>(l)) errorlimit(fs,l,m) /* ** nodes for block list (list of active blocks) */ typedef struct BlockCnt { struct BlockCnt *previous; /* chain */ int breaklist; /* list of jumps out of this loop */ lu_byte nactvar; /* # active locals outside the breakable structure */ lu_byte upval; /* true if some variable in the block is an upvalue */ lu_byte isbreakable; /* true if `block' is a loop */ } BlockCnt; /* ** prototypes for recursive non-terminal functions */ static void chunk (LexState *ls); static void expr (LexState *ls, expdesc *v); static void anchor_token (LexState *ls) { if (ls->t.token == TK_NAME || ls->t.token == TK_STRING) { TString *ts = ls->t.seminfo.ts; luaX_newstring(ls, getstr(ts), ts->tsv.len); } } static void error_expected (LexState *ls, int token) { luaX_syntaxerror(ls, luaO_pushfstring(ls->L, LUA_QS " expected", luaX_token2str(ls, token))); } static void errorlimit (FuncState *fs, int limit, const char *what) { const char *msg = (fs->f->linedefined == 0) ? luaO_pushfstring(fs->L, "main function has more than %d %s", limit, what) : luaO_pushfstring(fs->L, "function at line %d has more than %d %s", fs->f->linedefined, limit, what); luaX_lexerror(fs->ls, msg, 0); } static int testnext (LexState *ls, int c) { if (ls->t.token == c) { luaX_next(ls); return 1; } else return 0; } static void check (LexState *ls, int c) { if (ls->t.token != c) error_expected(ls, c); } static void checknext (LexState *ls, int c) { check(ls, c); luaX_next(ls); } #define check_condition(ls,c,msg) { if (!(c)) luaX_syntaxerror(ls, msg); } static void check_match (LexState *ls, int what, int who, int where) { if (!testnext(ls, what)) { if (where == ls->linenumber) error_expected(ls, what); else { luaX_syntaxerror(ls, luaO_pushfstring(ls->L, LUA_QS " expected (to close " LUA_QS " at line %d)", luaX_token2str(ls, what), luaX_token2str(ls, who), where)); } } } static TString *str_checkname (LexState *ls) { TString *ts; check(ls, TK_NAME); ts = ls->t.seminfo.ts; luaX_next(ls); return ts; } static void init_exp (expdesc *e, expkind k, int i) { e->f = e->t = NO_JUMP; e->k = k; e->u.s.info = i; } static void codestring (LexState *ls, expdesc *e, TString *s) { init_exp(e, VK, luaK_stringK(ls->fs, s)); } static void checkname(LexState *ls, expdesc *e) { codestring(ls, e, str_checkname(ls)); } static int registerlocalvar (LexState *ls, TString *varname) { FuncState *fs = ls->fs; Proto *f = fs->f; int oldsize = f->sizelocvars; luaM_growvector(ls->L, f->locvars, fs->nlocvars, f->sizelocvars, LocVar, SHRT_MAX, "too many local variables"); while (oldsize < f->sizelocvars) f->locvars[oldsize++].varname = NULL; f->locvars[fs->nlocvars].varname = varname; luaC_objbarrier(ls->L, f, varname); return fs->nlocvars++; } #define new_localvarliteral(ls,v,n) \ new_localvar(ls, luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char))-1), n) static void new_localvar (LexState *ls, TString *name, int n) { FuncState *fs = ls->fs; luaY_checklimit(fs, fs->nactvar+n+1, LUAI_MAXVARS, "local variables"); fs->actvar[fs->nactvar+n] = cast(unsigned short, registerlocalvar(ls, name)); } static void adjustlocalvars (LexState *ls, int nvars) { FuncState *fs = ls->fs; fs->nactvar = cast_byte(fs->nactvar + nvars); for (; nvars; nvars--) { getlocvar(fs, fs->nactvar - nvars).startpc = fs->pc; } } static void removevars (LexState *ls, int tolevel) { FuncState *fs = ls->fs; while (fs->nactvar > tolevel) getlocvar(fs, --fs->nactvar).endpc = fs->pc; } static int indexupvalue (FuncState *fs, TString *name, expdesc *v) { int i; Proto *f = fs->f; int oldsize = f->sizeupvalues; for (i=0; inups; i++) { if (fs->upvalues[i].k == v->k && fs->upvalues[i].info == v->u.s.info) { lua_assert(f->upvalues[i] == name); return i; } } /* new one */ luaY_checklimit(fs, f->nups + 1, LUAI_MAXUPVALUES, "upvalues"); luaM_growvector(fs->L, f->upvalues, f->nups, f->sizeupvalues, TString *, MAX_INT, ""); while (oldsize < f->sizeupvalues) f->upvalues[oldsize++] = NULL; f->upvalues[f->nups] = name; luaC_objbarrier(fs->L, f, name); lua_assert(v->k == VLOCAL || v->k == VUPVAL); fs->upvalues[f->nups].k = cast_byte(v->k); fs->upvalues[f->nups].info = cast_byte(v->u.s.info); return f->nups++; } static int searchvar (FuncState *fs, TString *n) { int i; for (i=fs->nactvar-1; i >= 0; i--) { if (n == getlocvar(fs, i).varname) return i; } return -1; /* not found */ } static void markupval (FuncState *fs, int level) { BlockCnt *bl = fs->bl; while (bl && bl->nactvar > level) bl = bl->previous; if (bl) bl->upval = 1; } static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { if (fs == NULL) { /* no more levels? */ init_exp(var, VGLOBAL, NO_REG); /* default is global variable */ return VGLOBAL; } else { int v = searchvar(fs, n); /* look up at current level */ if (v >= 0) { init_exp(var, VLOCAL, v); if (!base) markupval(fs, v); /* local will be used as an upval */ return VLOCAL; } else { /* not found at current level; try upper one */ if (singlevaraux(fs->prev, n, var, 0) == VGLOBAL) return VGLOBAL; var->u.s.info = indexupvalue(fs, n, var); /* else was LOCAL or UPVAL */ var->k = VUPVAL; /* upvalue in this level */ return VUPVAL; } } } static void singlevar (LexState *ls, expdesc *var) { TString *varname = str_checkname(ls); FuncState *fs = ls->fs; if (singlevaraux(fs, varname, var, 1) == VGLOBAL) var->u.s.info = luaK_stringK(fs, varname); /* info points to global name */ } static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) { FuncState *fs = ls->fs; int extra = nvars - nexps; if (hasmultret(e->k)) { extra++; /* includes call itself */ if (extra < 0) extra = 0; luaK_setreturns(fs, e, extra); /* last exp. provides the difference */ if (extra > 1) luaK_reserveregs(fs, extra-1); } else { if (e->k != VVOID) luaK_exp2nextreg(fs, e); /* close last expression */ if (extra > 0) { int reg = fs->freereg; luaK_reserveregs(fs, extra); luaK_nil(fs, reg, extra); } } } static void enterlevel (LexState *ls) { if (++ls->L->nCcalls > LUAI_MAXCCALLS) luaX_lexerror(ls, "chunk has too many syntax levels", 0); } #define leavelevel(ls) ((ls)->L->nCcalls--) static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isbreakable) { bl->breaklist = NO_JUMP; bl->isbreakable = isbreakable; bl->nactvar = fs->nactvar; bl->upval = 0; bl->previous = fs->bl; fs->bl = bl; lua_assert(fs->freereg == fs->nactvar); } static void leaveblock (FuncState *fs) { BlockCnt *bl = fs->bl; fs->bl = bl->previous; removevars(fs->ls, bl->nactvar); if (bl->upval) luaK_codeABC(fs, OP_CLOSE, bl->nactvar, 0, 0); /* a block either controls scope or breaks (never both) */ lua_assert(!bl->isbreakable || !bl->upval); lua_assert(bl->nactvar == fs->nactvar); fs->freereg = fs->nactvar; /* free registers */ luaK_patchtohere(fs, bl->breaklist); } static void pushclosure (LexState *ls, FuncState *func, expdesc *v) { FuncState *fs = ls->fs; Proto *f = fs->f; int oldsize = f->sizep; int i; luaM_growvector(ls->L, f->p, fs->np, f->sizep, Proto *, MAXARG_Bx, "constant table overflow"); while (oldsize < f->sizep) f->p[oldsize++] = NULL; f->p[fs->np++] = func->f; luaC_objbarrier(ls->L, f, func->f); init_exp(v, VRELOCABLE, luaK_codeABx(fs, OP_CLOSURE, 0, fs->np-1)); for (i=0; if->nups; i++) { OpCode o = (func->upvalues[i].k == VLOCAL) ? OP_MOVE : OP_GETUPVAL; luaK_codeABC(fs, o, 0, func->upvalues[i].info, 0); } } static void open_func (LexState *ls, FuncState *fs) { lua_State *L = ls->L; Proto *f = luaF_newproto(L); fs->f = f; fs->prev = ls->fs; /* linked list of funcstates */ fs->ls = ls; fs->L = L; ls->fs = fs; fs->pc = 0; fs->lasttarget = -1; fs->jpc = NO_JUMP; fs->freereg = 0; fs->nk = 0; fs->np = 0; fs->nlocvars = 0; fs->nactvar = 0; fs->bl = NULL; f->source = ls->source; f->maxstacksize = 2; /* registers 0/1 are always valid */ fs->h = luaH_new(L, 0, 0); /* anchor table of constants and prototype (to avoid being collected) */ sethvalue2s(L, L->top, fs->h); incr_top(L); setptvalue2s(L, L->top, f); incr_top(L); } static void close_func (LexState *ls) { lua_State *L = ls->L; FuncState *fs = ls->fs; Proto *f = fs->f; removevars(ls, 0); luaK_ret(fs, 0, 0); /* final return */ luaM_reallocvector(L, f->code, f->sizecode, fs->pc, Instruction); f->sizecode = fs->pc; luaM_reallocvector(L, f->lineinfo, f->sizelineinfo, fs->pc, int); f->sizelineinfo = fs->pc; luaM_reallocvector(L, f->k, f->sizek, fs->nk, TValue); f->sizek = fs->nk; luaM_reallocvector(L, f->p, f->sizep, fs->np, Proto *); f->sizep = fs->np; luaM_reallocvector(L, f->locvars, f->sizelocvars, fs->nlocvars, LocVar); f->sizelocvars = fs->nlocvars; luaM_reallocvector(L, f->upvalues, f->sizeupvalues, f->nups, TString *); f->sizeupvalues = f->nups; lua_assert(luaG_checkcode(f)); lua_assert(fs->bl == NULL); ls->fs = fs->prev; /* last token read was anchored in defunct function; must reanchor it */ if (fs) anchor_token(ls); L->top -= 2; /* remove table and prototype from the stack */ } Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name) { struct LexState lexstate; struct FuncState funcstate; lexstate.buff = buff; luaX_setinput(L, &lexstate, z, luaS_new(L, name)); open_func(&lexstate, &funcstate); funcstate.f->is_vararg = VARARG_ISVARARG; /* main func. is always vararg */ luaX_next(&lexstate); /* read first token */ chunk(&lexstate); check(&lexstate, TK_EOS); close_func(&lexstate); lua_assert(funcstate.prev == NULL); lua_assert(funcstate.f->nups == 0); lua_assert(lexstate.fs == NULL); return funcstate.f; } /*============================================================*/ /* GRAMMAR RULES */ /*============================================================*/ static void field (LexState *ls, expdesc *v) { /* field -> ['.' | ':'] NAME */ FuncState *fs = ls->fs; expdesc key; luaK_exp2anyreg(fs, v); luaX_next(ls); /* skip the dot or colon */ checkname(ls, &key); luaK_indexed(fs, v, &key); } static void yindex (LexState *ls, expdesc *v) { /* index -> '[' expr ']' */ luaX_next(ls); /* skip the '[' */ expr(ls, v); luaK_exp2val(ls->fs, v); checknext(ls, ']'); } /* ** {====================================================================== ** Rules for Constructors ** ======================================================================= */ struct ConsControl { expdesc v; /* last list item read */ expdesc *t; /* table descriptor */ int nh; /* total number of `record' elements */ int na; /* total number of array elements */ int tostore; /* number of array elements pending to be stored */ }; static void recfield (LexState *ls, struct ConsControl *cc) { /* recfield -> (NAME | `['exp1`]') = exp1 */ FuncState *fs = ls->fs; int reg = ls->fs->freereg; expdesc key, val; int rkkey; if (ls->t.token == TK_NAME) { luaY_checklimit(fs, cc->nh, MAX_INT, "items in a constructor"); checkname(ls, &key); } else /* ls->t.token == '[' */ yindex(ls, &key); cc->nh++; checknext(ls, '='); rkkey = luaK_exp2RK(fs, &key); expr(ls, &val); luaK_codeABC(fs, OP_SETTABLE, cc->t->u.s.info, rkkey, luaK_exp2RK(fs, &val)); fs->freereg = reg; /* free registers */ } static void closelistfield (FuncState *fs, struct ConsControl *cc) { if (cc->v.k == VVOID) return; /* there is no list item */ luaK_exp2nextreg(fs, &cc->v); cc->v.k = VVOID; if (cc->tostore == LFIELDS_PER_FLUSH) { luaK_setlist(fs, cc->t->u.s.info, cc->na, cc->tostore); /* flush */ cc->tostore = 0; /* no more items pending */ } } static void lastlistfield (FuncState *fs, struct ConsControl *cc) { if (cc->tostore == 0) return; if (hasmultret(cc->v.k)) { luaK_setmultret(fs, &cc->v); luaK_setlist(fs, cc->t->u.s.info, cc->na, LUA_MULTRET); cc->na--; /* do not count last expression (unknown number of elements) */ } else { if (cc->v.k != VVOID) luaK_exp2nextreg(fs, &cc->v); luaK_setlist(fs, cc->t->u.s.info, cc->na, cc->tostore); } } static void listfield (LexState *ls, struct ConsControl *cc) { expr(ls, &cc->v); luaY_checklimit(ls->fs, cc->na, MAX_INT, "items in a constructor"); cc->na++; cc->tostore++; } static void constructor (LexState *ls, expdesc *t) { /* constructor -> ?? */ FuncState *fs = ls->fs; int line = ls->linenumber; int pc = luaK_codeABC(fs, OP_NEWTABLE, 0, 0, 0); struct ConsControl cc; cc.na = cc.nh = cc.tostore = 0; cc.t = t; init_exp(t, VRELOCABLE, pc); init_exp(&cc.v, VVOID, 0); /* no value (yet) */ luaK_exp2nextreg(ls->fs, t); /* fix it at stack top (for gc) */ checknext(ls, '{'); do { lua_assert(cc.v.k == VVOID || cc.tostore > 0); if (ls->t.token == '}') break; closelistfield(fs, &cc); switch(ls->t.token) { case TK_NAME: { /* may be listfields or recfields */ luaX_lookahead(ls); if (ls->lookahead.token != '=') /* expression? */ listfield(ls, &cc); else recfield(ls, &cc); break; } case '[': { /* constructor_item -> recfield */ recfield(ls, &cc); break; } default: { /* constructor_part -> listfield */ listfield(ls, &cc); break; } } } while (testnext(ls, ',') || testnext(ls, ';')); check_match(ls, '}', '{', line); lastlistfield(fs, &cc); SETARG_B(fs->f->code[pc], luaO_int2fb(cc.na)); /* set initial array size */ SETARG_C(fs->f->code[pc], luaO_int2fb(cc.nh)); /* set initial table size */ } /* }====================================================================== */ static void parlist (LexState *ls) { /* parlist -> [ param { `,' param } ] */ FuncState *fs = ls->fs; Proto *f = fs->f; int nparams = 0; f->is_vararg = 0; if (ls->t.token != ')') { /* is `parlist' not empty? */ do { switch (ls->t.token) { case TK_NAME: { /* param -> NAME */ new_localvar(ls, str_checkname(ls), nparams++); break; } case TK_DOTS: { /* param -> `...' */ luaX_next(ls); #if defined(LUA_COMPAT_VARARG) /* use `arg' as default name */ new_localvarliteral(ls, "arg", nparams++); f->is_vararg = VARARG_HASARG | VARARG_NEEDSARG; #endif f->is_vararg |= VARARG_ISVARARG; break; } default: luaX_syntaxerror(ls, " or " LUA_QL("...") " expected"); } } while (!f->is_vararg && testnext(ls, ',')); } adjustlocalvars(ls, nparams); f->numparams = cast_byte(fs->nactvar - (f->is_vararg & VARARG_HASARG)); luaK_reserveregs(fs, fs->nactvar); /* reserve register for parameters */ } static void body (LexState *ls, expdesc *e, int needself, int line) { /* body -> `(' parlist `)' chunk END */ FuncState new_fs; open_func(ls, &new_fs); new_fs.f->linedefined = line; checknext(ls, '('); if (needself) { new_localvarliteral(ls, "self", 0); adjustlocalvars(ls, 1); } parlist(ls); checknext(ls, ')'); chunk(ls); new_fs.f->lastlinedefined = ls->linenumber; check_match(ls, TK_END, TK_FUNCTION, line); close_func(ls); pushclosure(ls, &new_fs, e); } static int explist1 (LexState *ls, expdesc *v) { /* explist1 -> expr { `,' expr } */ int n = 1; /* at least one expression */ expr(ls, v); while (testnext(ls, ',')) { luaK_exp2nextreg(ls->fs, v); expr(ls, v); n++; } return n; } static void funcargs (LexState *ls, expdesc *f) { FuncState *fs = ls->fs; expdesc args; int base, nparams; int line = ls->linenumber; switch (ls->t.token) { case '(': { /* funcargs -> `(' [ explist1 ] `)' */ if (line != ls->lastline) luaX_syntaxerror(ls,"ambiguous syntax (function call x new statement)"); luaX_next(ls); if (ls->t.token == ')') /* arg list is empty? */ args.k = VVOID; else { explist1(ls, &args); luaK_setmultret(fs, &args); } check_match(ls, ')', '(', line); break; } case '{': { /* funcargs -> constructor */ constructor(ls, &args); break; } case TK_STRING: { /* funcargs -> STRING */ codestring(ls, &args, ls->t.seminfo.ts); luaX_next(ls); /* must use `seminfo' before `next' */ break; } default: { luaX_syntaxerror(ls, "function arguments expected"); return; } } lua_assert(f->k == VNONRELOC); base = f->u.s.info; /* base register for call */ if (hasmultret(args.k)) nparams = LUA_MULTRET; /* open call */ else { if (args.k != VVOID) luaK_exp2nextreg(fs, &args); /* close last argument */ nparams = fs->freereg - (base+1); } init_exp(f, VCALL, luaK_codeABC(fs, OP_CALL, base, nparams+1, 2)); luaK_fixline(fs, line); fs->freereg = base+1; /* call remove function and arguments and leaves (unless changed) one result */ } /* ** {====================================================================== ** Expression parsing ** ======================================================================= */ static void prefixexp (LexState *ls, expdesc *v) { /* prefixexp -> NAME | '(' expr ')' */ switch (ls->t.token) { case '(': { int line = ls->linenumber; luaX_next(ls); expr(ls, v); check_match(ls, ')', '(', line); luaK_dischargevars(ls->fs, v); return; } case TK_NAME: { singlevar(ls, v); return; } default: { luaX_syntaxerror(ls, "unexpected symbol"); return; } } } static void primaryexp (LexState *ls, expdesc *v) { /* primaryexp -> prefixexp { `.' NAME | `[' exp `]' | `:' NAME funcargs | funcargs } */ FuncState *fs = ls->fs; prefixexp(ls, v); for (;;) { switch (ls->t.token) { case '.': { /* field */ field(ls, v); break; } case '[': { /* `[' exp1 `]' */ expdesc key; luaK_exp2anyreg(fs, v); yindex(ls, &key); luaK_indexed(fs, v, &key); break; } case ':': { /* `:' NAME funcargs */ expdesc key; luaX_next(ls); checkname(ls, &key); luaK_self(fs, v, &key); funcargs(ls, v); break; } case '(': case TK_STRING: case '{': { /* funcargs */ luaK_exp2nextreg(fs, v); funcargs(ls, v); break; } default: return; } } } static void simpleexp (LexState *ls, expdesc *v) { /* simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor | FUNCTION body | primaryexp */ switch (ls->t.token) { case TK_NUMBER: { init_exp(v, VKNUM, 0); v->u.nval = ls->t.seminfo.r; break; } case TK_STRING: { codestring(ls, v, ls->t.seminfo.ts); break; } case TK_NIL: { init_exp(v, VNIL, 0); break; } case TK_TRUE: { init_exp(v, VTRUE, 0); break; } case TK_FALSE: { init_exp(v, VFALSE, 0); break; } case TK_DOTS: { /* vararg */ FuncState *fs = ls->fs; check_condition(ls, fs->f->is_vararg, "cannot use " LUA_QL("...") " outside a vararg function"); fs->f->is_vararg &= ~VARARG_NEEDSARG; /* don't need 'arg' */ init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, 1, 0)); break; } case '{': { /* constructor */ constructor(ls, v); return; } case TK_FUNCTION: { luaX_next(ls); body(ls, v, 0, ls->linenumber); return; } default: { primaryexp(ls, v); return; } } luaX_next(ls); } static UnOpr getunopr (int op) { switch (op) { case TK_NOT: return OPR_NOT; case '-': return OPR_MINUS; case '#': return OPR_LEN; default: return OPR_NOUNOPR; } } static BinOpr getbinopr (int op) { switch (op) { case '+': return OPR_ADD; case '-': return OPR_SUB; case '*': return OPR_MUL; case '/': return OPR_DIV; case '%': return OPR_MOD; case '^': return OPR_POW; case TK_CONCAT: return OPR_CONCAT; case TK_NE: return OPR_NE; case TK_EQ: return OPR_EQ; case '<': return OPR_LT; case TK_LE: return OPR_LE; case '>': return OPR_GT; case TK_GE: return OPR_GE; case TK_AND: return OPR_AND; case TK_OR: return OPR_OR; default: return OPR_NOBINOPR; } } static const struct { lu_byte left; /* left priority for each binary operator */ lu_byte right; /* right priority */ } priority[] = { /* ORDER OPR */ {6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, /* `+' `-' `/' `%' */ {10, 9}, {5, 4}, /* power and concat (right associative) */ {3, 3}, {3, 3}, /* equality and inequality */ {3, 3}, {3, 3}, {3, 3}, {3, 3}, /* order */ {2, 2}, {1, 1} /* logical (and/or) */ }; #define UNARY_PRIORITY 8 /* priority for unary operators */ /* ** subexpr -> (simpleexp | unop subexpr) { binop subexpr } ** where `binop' is any binary operator with a priority higher than `limit' */ static BinOpr subexpr (LexState *ls, expdesc *v, unsigned int limit) { BinOpr op; UnOpr uop; enterlevel(ls); uop = getunopr(ls->t.token); if (uop != OPR_NOUNOPR) { luaX_next(ls); subexpr(ls, v, UNARY_PRIORITY); luaK_prefix(ls->fs, uop, v); } else simpleexp(ls, v); /* expand while operators have priorities higher than `limit' */ op = getbinopr(ls->t.token); while (op != OPR_NOBINOPR && priority[op].left > limit) { expdesc v2; BinOpr nextop; luaX_next(ls); luaK_infix(ls->fs, op, v); /* read sub-expression with higher priority */ nextop = subexpr(ls, &v2, priority[op].right); luaK_posfix(ls->fs, op, v, &v2); op = nextop; } leavelevel(ls); return op; /* return first untreated operator */ } static void expr (LexState *ls, expdesc *v) { subexpr(ls, v, 0); } /* }==================================================================== */ /* ** {====================================================================== ** Rules for Statements ** ======================================================================= */ static int block_follow (int token) { switch (token) { case TK_ELSE: case TK_ELSEIF: case TK_END: case TK_UNTIL: case TK_EOS: return 1; default: return 0; } } static void block (LexState *ls) { /* block -> chunk */ FuncState *fs = ls->fs; BlockCnt bl; enterblock(fs, &bl, 0); chunk(ls); lua_assert(bl.breaklist == NO_JUMP); leaveblock(fs); } /* ** structure to chain all variables in the left-hand side of an ** assignment */ struct LHS_assign { struct LHS_assign *prev; expdesc v; /* variable (global, local, upvalue, or indexed) */ }; /* ** check whether, in an assignment to a local variable, the local variable ** is needed in a previous assignment (to a table). If so, save original ** local value in a safe place and use this safe copy in the previous ** assignment. */ static void check_conflict (LexState *ls, struct LHS_assign *lh, expdesc *v) { FuncState *fs = ls->fs; int extra = fs->freereg; /* eventual position to save local variable */ int conflict = 0; for (; lh; lh = lh->prev) { if (lh->v.k == VINDEXED) { if (lh->v.u.s.info == v->u.s.info) { /* conflict? */ conflict = 1; lh->v.u.s.info = extra; /* previous assignment will use safe copy */ } if (lh->v.u.s.aux == v->u.s.info) { /* conflict? */ conflict = 1; lh->v.u.s.aux = extra; /* previous assignment will use safe copy */ } } } if (conflict) { luaK_codeABC(fs, OP_MOVE, fs->freereg, v->u.s.info, 0); /* make copy */ luaK_reserveregs(fs, 1); } } static void assignment (LexState *ls, struct LHS_assign *lh, int nvars) { expdesc e; check_condition(ls, VLOCAL <= lh->v.k && lh->v.k <= VINDEXED, "syntax error"); if (testnext(ls, ',')) { /* assignment -> `,' primaryexp assignment */ struct LHS_assign nv; nv.prev = lh; primaryexp(ls, &nv.v); if (nv.v.k == VLOCAL) check_conflict(ls, lh, &nv.v); luaY_checklimit(ls->fs, nvars, LUAI_MAXCCALLS - ls->L->nCcalls, "variables in assignment"); assignment(ls, &nv, nvars+1); } else { /* assignment -> `=' explist1 */ int nexps; checknext(ls, '='); nexps = explist1(ls, &e); if (nexps != nvars) { adjust_assign(ls, nvars, nexps, &e); if (nexps > nvars) ls->fs->freereg -= nexps - nvars; /* remove extra values */ } else { luaK_setoneret(ls->fs, &e); /* close last expression */ luaK_storevar(ls->fs, &lh->v, &e); return; /* avoid default */ } } init_exp(&e, VNONRELOC, ls->fs->freereg-1); /* default assignment */ luaK_storevar(ls->fs, &lh->v, &e); } static int cond (LexState *ls) { /* cond -> exp */ expdesc v; expr(ls, &v); /* read condition */ if (v.k == VNIL) v.k = VFALSE; /* `falses' are all equal here */ luaK_goiftrue(ls->fs, &v); return v.f; } static void breakstat (LexState *ls) { FuncState *fs = ls->fs; BlockCnt *bl = fs->bl; int upval = 0; while (bl && !bl->isbreakable) { upval |= bl->upval; bl = bl->previous; } if (!bl) luaX_syntaxerror(ls, "no loop to break"); if (upval) luaK_codeABC(fs, OP_CLOSE, bl->nactvar, 0, 0); luaK_concat(fs, &bl->breaklist, luaK_jump(fs)); } static void whilestat (LexState *ls, int line) { /* whilestat -> WHILE cond DO block END */ FuncState *fs = ls->fs; int whileinit; int condexit; BlockCnt bl; luaX_next(ls); /* skip WHILE */ whileinit = luaK_getlabel(fs); condexit = cond(ls); enterblock(fs, &bl, 1); checknext(ls, TK_DO); block(ls); luaK_patchlist(fs, luaK_jump(fs), whileinit); check_match(ls, TK_END, TK_WHILE, line); leaveblock(fs); luaK_patchtohere(fs, condexit); /* false conditions finish the loop */ } static void repeatstat (LexState *ls, int line) { /* repeatstat -> REPEAT block UNTIL cond */ int condexit; FuncState *fs = ls->fs; int repeat_init = luaK_getlabel(fs); BlockCnt bl1, bl2; enterblock(fs, &bl1, 1); /* loop block */ enterblock(fs, &bl2, 0); /* scope block */ luaX_next(ls); /* skip REPEAT */ chunk(ls); check_match(ls, TK_UNTIL, TK_REPEAT, line); condexit = cond(ls); /* read condition (inside scope block) */ if (!bl2.upval) { /* no upvalues? */ leaveblock(fs); /* finish scope */ luaK_patchlist(ls->fs, condexit, repeat_init); /* close the loop */ } else { /* complete semantics when there are upvalues */ breakstat(ls); /* if condition then break */ luaK_patchtohere(ls->fs, condexit); /* else... */ leaveblock(fs); /* finish scope... */ luaK_patchlist(ls->fs, luaK_jump(fs), repeat_init); /* and repeat */ } leaveblock(fs); /* finish loop */ } static int exp1 (LexState *ls) { expdesc e; int k; expr(ls, &e); k = e.k; luaK_exp2nextreg(ls->fs, &e); return k; } static void forbody (LexState *ls, int base, int line, int nvars, int isnum) { /* forbody -> DO block */ BlockCnt bl; FuncState *fs = ls->fs; int prep, endfor; adjustlocalvars(ls, 3); /* control variables */ checknext(ls, TK_DO); prep = isnum ? luaK_codeAsBx(fs, OP_FORPREP, base, NO_JUMP) : luaK_jump(fs); enterblock(fs, &bl, 0); /* scope for declared variables */ adjustlocalvars(ls, nvars); luaK_reserveregs(fs, nvars); block(ls); leaveblock(fs); /* end of scope for declared variables */ luaK_patchtohere(fs, prep); endfor = (isnum) ? luaK_codeAsBx(fs, OP_FORLOOP, base, NO_JUMP) : luaK_codeABC(fs, OP_TFORLOOP, base, 0, nvars); luaK_fixline(fs, line); /* pretend that `OP_FOR' starts the loop */ luaK_patchlist(fs, (isnum ? endfor : luaK_jump(fs)), prep + 1); } static void fornum (LexState *ls, TString *varname, int line) { /* fornum -> NAME = exp1,exp1[,exp1] forbody */ FuncState *fs = ls->fs; int base = fs->freereg; new_localvarliteral(ls, "(for index)", 0); new_localvarliteral(ls, "(for limit)", 1); new_localvarliteral(ls, "(for step)", 2); new_localvar(ls, varname, 3); checknext(ls, '='); exp1(ls); /* initial value */ checknext(ls, ','); exp1(ls); /* limit */ if (testnext(ls, ',')) exp1(ls); /* optional step */ else { /* default step = 1 */ luaK_codeABx(fs, OP_LOADK, fs->freereg, luaK_numberK(fs, 1)); luaK_reserveregs(fs, 1); } forbody(ls, base, line, 1, 1); } static void forlist (LexState *ls, TString *indexname) { /* forlist -> NAME {,NAME} IN explist1 forbody */ FuncState *fs = ls->fs; expdesc e; int nvars = 0; int line; int base = fs->freereg; /* create control variables */ new_localvarliteral(ls, "(for generator)", nvars++); new_localvarliteral(ls, "(for state)", nvars++); new_localvarliteral(ls, "(for control)", nvars++); /* create declared variables */ new_localvar(ls, indexname, nvars++); while (testnext(ls, ',')) new_localvar(ls, str_checkname(ls), nvars++); checknext(ls, TK_IN); line = ls->linenumber; adjust_assign(ls, 3, explist1(ls, &e), &e); luaK_checkstack(fs, 3); /* extra space to call generator */ forbody(ls, base, line, nvars - 3, 0); } static void forstat (LexState *ls, int line) { /* forstat -> FOR (fornum | forlist) END */ FuncState *fs = ls->fs; TString *varname; BlockCnt bl; enterblock(fs, &bl, 1); /* scope for loop and control variables */ luaX_next(ls); /* skip `for' */ varname = str_checkname(ls); /* first variable name */ switch (ls->t.token) { case '=': fornum(ls, varname, line); break; case ',': case TK_IN: forlist(ls, varname); break; default: luaX_syntaxerror(ls, LUA_QL("=") " or " LUA_QL("in") " expected"); } check_match(ls, TK_END, TK_FOR, line); leaveblock(fs); /* loop scope (`break' jumps to this point) */ } static int test_then_block (LexState *ls) { /* test_then_block -> [IF | ELSEIF] cond THEN block */ int condexit; luaX_next(ls); /* skip IF or ELSEIF */ condexit = cond(ls); checknext(ls, TK_THEN); block(ls); /* `then' part */ return condexit; } static void ifstat (LexState *ls, int line) { /* ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block] END */ FuncState *fs = ls->fs; int flist; int escapelist = NO_JUMP; flist = test_then_block(ls); /* IF cond THEN block */ while (ls->t.token == TK_ELSEIF) { luaK_concat(fs, &escapelist, luaK_jump(fs)); luaK_patchtohere(fs, flist); flist = test_then_block(ls); /* ELSEIF cond THEN block */ } if (ls->t.token == TK_ELSE) { luaK_concat(fs, &escapelist, luaK_jump(fs)); luaK_patchtohere(fs, flist); luaX_next(ls); /* skip ELSE (after patch, for correct line info) */ block(ls); /* `else' part */ } else luaK_concat(fs, &escapelist, flist); luaK_patchtohere(fs, escapelist); check_match(ls, TK_END, TK_IF, line); } static void localfunc (LexState *ls) { expdesc v, b; FuncState *fs = ls->fs; new_localvar(ls, str_checkname(ls), 0); init_exp(&v, VLOCAL, fs->freereg); luaK_reserveregs(fs, 1); adjustlocalvars(ls, 1); body(ls, &b, 0, ls->linenumber); luaK_storevar(fs, &v, &b); /* debug information will only see the variable after this point! */ getlocvar(fs, fs->nactvar - 1).startpc = fs->pc; } static void localstat (LexState *ls) { /* stat -> LOCAL NAME {`,' NAME} [`=' explist1] */ int nvars = 0; int nexps; expdesc e; do { new_localvar(ls, str_checkname(ls), nvars++); } while (testnext(ls, ',')); if (testnext(ls, '=')) nexps = explist1(ls, &e); else { e.k = VVOID; nexps = 0; } adjust_assign(ls, nvars, nexps, &e); adjustlocalvars(ls, nvars); } static int funcname (LexState *ls, expdesc *v) { /* funcname -> NAME {field} [`:' NAME] */ int needself = 0; singlevar(ls, v); while (ls->t.token == '.') field(ls, v); if (ls->t.token == ':') { needself = 1; field(ls, v); } return needself; } static void funcstat (LexState *ls, int line) { /* funcstat -> FUNCTION funcname body */ int needself; expdesc v, b; luaX_next(ls); /* skip FUNCTION */ needself = funcname(ls, &v); body(ls, &b, needself, line); luaK_storevar(ls->fs, &v, &b); luaK_fixline(ls->fs, line); /* definition `happens' in the first line */ } static void exprstat (LexState *ls) { /* stat -> func | assignment */ FuncState *fs = ls->fs; struct LHS_assign v; primaryexp(ls, &v.v); if (v.v.k == VCALL) /* stat -> func */ SETARG_C(getcode(fs, &v.v), 1); /* call statement uses no results */ else { /* stat -> assignment */ v.prev = NULL; assignment(ls, &v, 1); } } static void retstat (LexState *ls) { /* stat -> RETURN explist */ FuncState *fs = ls->fs; expdesc e; int first, nret; /* registers with returned values */ luaX_next(ls); /* skip RETURN */ if (block_follow(ls->t.token) || ls->t.token == ';') first = nret = 0; /* return no values */ else { nret = explist1(ls, &e); /* optional return values */ if (hasmultret(e.k)) { luaK_setmultret(fs, &e); if (e.k == VCALL && nret == 1) { /* tail call? */ SET_OPCODE(getcode(fs,&e), OP_TAILCALL); lua_assert(GETARG_A(getcode(fs,&e)) == fs->nactvar); } first = fs->nactvar; nret = LUA_MULTRET; /* return all values */ } else { if (nret == 1) /* only one single value? */ first = luaK_exp2anyreg(fs, &e); else { luaK_exp2nextreg(fs, &e); /* values must go to the `stack' */ first = fs->nactvar; /* return all `active' values */ lua_assert(nret == fs->freereg - first); } } } luaK_ret(fs, first, nret); } static int statement (LexState *ls) { int line = ls->linenumber; /* may be needed for error messages */ switch (ls->t.token) { case TK_IF: { /* stat -> ifstat */ ifstat(ls, line); return 0; } case TK_WHILE: { /* stat -> whilestat */ whilestat(ls, line); return 0; } case TK_DO: { /* stat -> DO block END */ luaX_next(ls); /* skip DO */ block(ls); check_match(ls, TK_END, TK_DO, line); return 0; } case TK_FOR: { /* stat -> forstat */ forstat(ls, line); return 0; } case TK_REPEAT: { /* stat -> repeatstat */ repeatstat(ls, line); return 0; } case TK_FUNCTION: { funcstat(ls, line); /* stat -> funcstat */ return 0; } case TK_LOCAL: { /* stat -> localstat */ luaX_next(ls); /* skip LOCAL */ if (testnext(ls, TK_FUNCTION)) /* local function? */ localfunc(ls); else localstat(ls); return 0; } case TK_RETURN: { /* stat -> retstat */ retstat(ls); return 1; /* must be last statement */ } case TK_BREAK: { /* stat -> breakstat */ luaX_next(ls); /* skip BREAK */ breakstat(ls); return 1; /* must be last statement */ } default: { exprstat(ls); return 0; /* to avoid warnings */ } } } static void chunk (LexState *ls) { /* chunk -> { stat [`;'] } */ int islast = 0; enterlevel(ls); while (!islast && !block_follow(ls->t.token)) { islast = statement(ls); testnext(ls, ';'); lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg && ls->fs->freereg >= ls->fs->nactvar); ls->fs->freereg = ls->fs->nactvar; /* free registers */ } leavelevel(ls); } /* }====================================================================== */ ================================================ FILE: src/lparser.h ================================================ /* ** $Id: lparser.h,v 1.57.1.1 2007/12/27 13:02:25 roberto Exp $ ** Lua Parser ** See Copyright Notice in lua.h */ #ifndef lparser_h #define lparser_h #include "llimits.h" #include "lobject.h" #include "lzio.h" /* ** Expression descriptor */ typedef enum { VVOID, /* no value */ VNIL, VTRUE, VFALSE, VK, /* info = index of constant in `k' */ VKNUM, /* nval = numerical value */ VLOCAL, /* info = local register */ VUPVAL, /* info = index of upvalue in `upvalues' */ VGLOBAL, /* info = index of table; aux = index of global name in `k' */ VINDEXED, /* info = table register; aux = index register (or `k') */ VJMP, /* info = instruction pc */ VRELOCABLE, /* info = instruction pc */ VNONRELOC, /* info = result register */ VCALL, /* info = instruction pc */ VVARARG /* info = instruction pc */ } expkind; typedef struct expdesc { expkind k; union { struct { int info, aux; } s; lua_Number nval; } u; int t; /* patch list of `exit when true' */ int f; /* patch list of `exit when false' */ } expdesc; typedef struct upvaldesc { lu_byte k; lu_byte info; } upvaldesc; struct BlockCnt; /* defined in lparser.c */ /* state needed to generate code for a given function */ typedef struct FuncState { Proto *f; /* current function header */ Table *h; /* table to find (and reuse) elements in `k' */ struct FuncState *prev; /* enclosing function */ struct LexState *ls; /* lexical state */ struct lua_State *L; /* copy of the Lua state */ struct BlockCnt *bl; /* chain of current blocks */ int pc; /* next position to code (equivalent to `ncode') */ int lasttarget; /* `pc' of last `jump target' */ int jpc; /* list of pending jumps to `pc' */ int freereg; /* first free register */ int nk; /* number of elements in `k' */ int np; /* number of elements in `p' */ short nlocvars; /* number of elements in `locvars' */ lu_byte nactvar; /* number of active local variables */ upvaldesc upvalues[LUAI_MAXUPVALUES]; /* upvalues */ unsigned short actvar[LUAI_MAXVARS]; /* declared-variable stack */ } FuncState; LUAI_FUNC Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name); #endif ================================================ FILE: src/lstate.c ================================================ /* ** $Id: lstate.c,v 2.36.1.2 2008/01/03 15:20:39 roberto Exp $ ** Global State ** See Copyright Notice in lua.h */ #include #define lstate_c #define LUA_CORE #include "lua.h" #include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "lgc.h" #include "llex.h" #include "lmem.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "ltm.h" /* ** Main thread combines a thread state and the global state */ typedef struct LG { lua_State l; global_State g; } LG; static void stack_init (lua_State *L1, lua_State *L) { /* initialize CallInfo array */ L1->base_ci = luaM_newvector(L, BASIC_CI_SIZE, CallInfo); L1->ci = L1->base_ci; L1->size_ci = BASIC_CI_SIZE; L1->end_ci = L1->base_ci + L1->size_ci - 1; /* initialize stack array */ L1->stack = luaM_newvector(L, BASIC_STACK_SIZE + EXTRA_STACK, TValue); L1->stacksize = BASIC_STACK_SIZE + EXTRA_STACK; L1->top = L1->stack; L1->stack_last = L1->stack+(L1->stacksize - EXTRA_STACK)-1; /* initialize first ci */ L1->ci->func = L1->top; setnilvalue(L1->top++); /* `function' entry for this `ci' */ L1->base = L1->ci->base = L1->top; L1->ci->top = L1->top + LUA_MINSTACK; } static void freestack (lua_State *L, lua_State *L1) { luaM_freearray(L, L1->base_ci, L1->size_ci, CallInfo); luaM_freearray(L, L1->stack, L1->stacksize, TValue); } /* ** open parts that may cause memory-allocation errors */ static void f_luaopen (lua_State *L, void *ud) { global_State *g = G(L); UNUSED(ud); stack_init(L, L); /* init stack */ sethvalue(L, gt(L), luaH_new(L, 0, 2)); /* table of globals */ sethvalue(L, registry(L), luaH_new(L, 0, 2)); /* registry */ luaS_resize(L, MINSTRTABSIZE); /* initial size of string table */ luaT_init(L); luaX_init(L); luaS_fix(luaS_newliteral(L, MEMERRMSG)); g->GCthreshold = 4*g->totalbytes; } static void preinit_state (lua_State *L, global_State *g) { G(L) = g; L->stack = NULL; L->stacksize = 0; L->errorJmp = NULL; L->hook = NULL; L->hookmask = 0; L->basehookcount = 0; L->allowhook = 1; resethookcount(L); L->openupval = NULL; L->size_ci = 0; L->nCcalls = L->baseCcalls = 0; L->status = 0; L->base_ci = L->ci = NULL; L->savedpc = NULL; L->errfunc = 0; setnilvalue(gt(L)); } static void close_state (lua_State *L) { global_State *g = G(L); luaF_close(L, L->stack); /* close all upvalues for this thread */ luaC_freeall(L); /* collect all objects */ lua_assert(g->rootgc == obj2gco(L)); lua_assert(g->strt.nuse == 0); luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size, TString *); luaZ_freebuffer(L, &g->buff); freestack(L, L); lua_assert(g->totalbytes == sizeof(LG)); (*g->frealloc)(g->ud, cast(lu_byte*, L), sizeof(LG), 0); } lua_State *luaE_newthread (lua_State *L) { lua_State *L1 = cast(lua_State*, luaM_malloc(L, sizeof(lua_State))); luaC_link(L, obj2gco(L1), LUA_TTHREAD); preinit_state(L1, G(L)); stack_init(L1, L); /* init stack */ setobj2n(L, gt(L1), gt(L)); /* share table of globals */ L1->hookmask = L->hookmask; L1->basehookcount = L->basehookcount; L1->hook = L->hook; resethookcount(L1); lua_assert(iswhite(obj2gco(L1))); return L1; } void luaE_freethread (lua_State *L, lua_State *L1) { luaF_close(L1, L1->stack); /* close all upvalues for this thread */ lua_assert(L1->openupval == NULL); freestack(L, L1); luaM_freemem(L, cast(lu_byte*, L1), sizeof(lua_State)); } LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { int i; lua_State *L; global_State *g; void *l = (*f)(ud, NULL, 0, sizeof(LG)); if (l == NULL) return NULL; L = cast(lua_State*, l); g = &cast(LG *, l)->g; L->next = NULL; L->tt = LUA_TTHREAD; g->currentwhite = bit2mask(WHITE0BIT, FIXEDBIT); L->marked = luaC_white(g); set2bits(L->marked, FIXEDBIT, SFIXEDBIT); preinit_state(L, g); g->frealloc = f; g->ud = ud; g->mainthread = L; g->uvhead.u.l.prev = &g->uvhead; g->uvhead.u.l.next = &g->uvhead; g->GCthreshold = 0; /* mark it as unfinished state */ g->strt.size = 0; g->strt.nuse = 0; g->strt.hash = NULL; setnilvalue(registry(L)); luaZ_initbuffer(L, &g->buff); g->panic = NULL; g->gcstate = GCSpause; g->rootgc = obj2gco(L); g->sweepstrgc = 0; g->sweepgc = &g->rootgc; g->gray = NULL; g->grayagain = NULL; g->weak = NULL; g->tmudata = NULL; g->totalbytes = sizeof(LG); g->gcpause = LUAI_GCPAUSE; g->gcstepmul = LUAI_GCMUL; g->gcdept = 0; for (i=0; imt[i] = NULL; if (luaD_rawrunprotected(L, f_luaopen, NULL) != 0) { /* memory allocation error: free partial state */ close_state(L); L = NULL; } return L; } static void callallgcTM (lua_State *L, void *ud) { UNUSED(ud); luaC_callGCTM(L); /* call GC metamethods for all udata */ } LUA_API void lua_close (lua_State *L) { L = G(L)->mainthread; /* only the main thread can be closed */ lua_lock(L); luaF_close(L, L->stack); /* close all upvalues for this thread */ luaC_separateudata(L, 1); /* separate udata that have GC metamethods */ L->errfunc = 0; /* no error function during GC metamethods */ do { /* repeat until no more errors */ L->ci = L->base_ci; L->base = L->top = L->ci->base; L->nCcalls = L->baseCcalls = 0; } while (luaD_rawrunprotected(L, callallgcTM, NULL) != 0); lua_assert(G(L)->tmudata == NULL); close_state(L); } ================================================ FILE: src/lstate.h ================================================ /* ** $Id: lstate.h,v 2.24.1.2 2008/01/03 15:20:39 roberto Exp $ ** Global State ** See Copyright Notice in lua.h */ #ifndef lstate_h #define lstate_h #include "lua.h" #include "lobject.h" #include "ltm.h" #include "lzio.h" struct lua_longjmp; /* defined in ldo.c */ /* table of globals */ #define gt(L) (&L->l_gt) /* registry */ #define registry(L) (&G(L)->l_registry) /* extra stack space to handle TM calls and some other extras */ #define EXTRA_STACK 5 #define BASIC_CI_SIZE 8 #define BASIC_STACK_SIZE (2*LUA_MINSTACK) typedef struct stringtable { GCObject **hash; lu_int32 nuse; /* number of elements */ int size; } stringtable; /* ** informations about a call */ typedef struct CallInfo { StkId base; /* base for this function */ StkId func; /* function index in the stack */ StkId top; /* top for this function */ const Instruction *savedpc; int nresults; /* expected number of results from this function */ int tailcalls; /* number of tail calls lost under this entry */ } CallInfo; #define curr_func(L) (clvalue(L->ci->func)) #define ci_func(ci) (clvalue((ci)->func)) #define f_isLua(ci) (!ci_func(ci)->c.isC) #define isLua(ci) (ttisfunction((ci)->func) && f_isLua(ci)) /* ** `global state', shared by all threads of this state */ typedef struct global_State { stringtable strt; /* hash table for strings */ lua_Alloc frealloc; /* function to reallocate memory */ void *ud; /* auxiliary data to `frealloc' */ lu_byte currentwhite; lu_byte gcstate; /* state of garbage collector */ int sweepstrgc; /* position of sweep in `strt' */ GCObject *rootgc; /* list of all collectable objects */ GCObject **sweepgc; /* position of sweep in `rootgc' */ GCObject *gray; /* list of gray objects */ GCObject *grayagain; /* list of objects to be traversed atomically */ GCObject *weak; /* list of weak tables (to be cleared) */ GCObject *tmudata; /* last element of list of userdata to be GC */ Mbuffer buff; /* temporary buffer for string concatentation */ lu_mem GCthreshold; lu_mem totalbytes; /* number of bytes currently allocated */ lu_mem estimate; /* an estimate of number of bytes actually in use */ lu_mem gcdept; /* how much GC is `behind schedule' */ int gcpause; /* size of pause between successive GCs */ int gcstepmul; /* GC `granularity' */ lua_CFunction panic; /* to be called in unprotected errors */ TValue l_registry; struct lua_State *mainthread; UpVal uvhead; /* head of double-linked list of all open upvalues */ struct Table *mt[NUM_TAGS]; /* metatables for basic types */ TString *tmname[TM_N]; /* array with tag-method names */ } global_State; /* ** `per thread' state */ struct lua_State { CommonHeader; lu_byte status; StkId top; /* first free slot in the stack */ StkId base; /* base of current function */ global_State *l_G; CallInfo *ci; /* call info for current function */ const Instruction *savedpc; /* `savedpc' of current function */ StkId stack_last; /* last free slot in the stack */ StkId stack; /* stack base */ CallInfo *end_ci; /* points after end of ci array*/ CallInfo *base_ci; /* array of CallInfo's */ int stacksize; int size_ci; /* size of array `base_ci' */ unsigned short nCcalls; /* number of nested C calls */ unsigned short baseCcalls; /* nested C calls when resuming coroutine */ lu_byte hookmask; lu_byte allowhook; int basehookcount; int hookcount; lua_Hook hook; TValue l_gt; /* table of globals */ TValue env; /* temporary place for environments */ GCObject *openupval; /* list of open upvalues in this stack */ GCObject *gclist; struct lua_longjmp *errorJmp; /* current error recover point */ ptrdiff_t errfunc; /* current error handling function (stack index) */ }; #define G(L) (L->l_G) /* ** Union of all collectable objects */ union GCObject { GCheader gch; union TString ts; union Udata u; union Closure cl; struct Table h; struct Proto p; struct UpVal uv; struct lua_State th; /* thread */ }; /* macros to convert a GCObject into a specific value */ #define rawgco2ts(o) check_exp((o)->gch.tt == LUA_TSTRING, &((o)->ts)) #define gco2ts(o) (&rawgco2ts(o)->tsv) #define rawgco2u(o) check_exp((o)->gch.tt == LUA_TUSERDATA, &((o)->u)) #define gco2u(o) (&rawgco2u(o)->uv) #define gco2cl(o) check_exp((o)->gch.tt == LUA_TFUNCTION, &((o)->cl)) #define gco2h(o) check_exp((o)->gch.tt == LUA_TTABLE, &((o)->h)) #define gco2p(o) check_exp((o)->gch.tt == LUA_TPROTO, &((o)->p)) #define gco2uv(o) check_exp((o)->gch.tt == LUA_TUPVAL, &((o)->uv)) #define ngcotouv(o) \ check_exp((o) == NULL || (o)->gch.tt == LUA_TUPVAL, &((o)->uv)) #define gco2th(o) check_exp((o)->gch.tt == LUA_TTHREAD, &((o)->th)) /* macro to convert any Lua object into a GCObject */ #define obj2gco(v) (cast(GCObject *, (v))) LUAI_FUNC lua_State *luaE_newthread (lua_State *L); LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1); #endif ================================================ FILE: src/lstring.c ================================================ /* ** $Id: lstring.c,v 2.8.1.1 2007/12/27 13:02:25 roberto Exp $ ** String table (keeps all strings handled by Lua) ** See Copyright Notice in lua.h */ #include #define lstring_c #define LUA_CORE #include "lua.h" #include "lmem.h" #include "lobject.h" #include "lstate.h" #include "lstring.h" void luaS_resize (lua_State *L, int newsize) { GCObject **newhash; stringtable *tb; int i; if (G(L)->gcstate == GCSsweepstring) return; /* cannot resize during GC traverse */ newhash = luaM_newvector(L, newsize, GCObject *); tb = &G(L)->strt; for (i=0; isize; i++) { GCObject *p = tb->hash[i]; while (p) { /* for each node in the list */ GCObject *next = p->gch.next; /* save next */ unsigned int h = gco2ts(p)->hash; int h1 = lmod(h, newsize); /* new position */ lua_assert(cast_int(h%newsize) == lmod(h, newsize)); p->gch.next = newhash[h1]; /* chain it */ newhash[h1] = p; p = next; } } luaM_freearray(L, tb->hash, tb->size, TString *); tb->size = newsize; tb->hash = newhash; } static TString *newlstr (lua_State *L, const char *str, size_t l, unsigned int h) { TString *ts; stringtable *tb; if (l+1 > (MAX_SIZET - sizeof(TString))/sizeof(char)) luaM_toobig(L); ts = cast(TString *, luaM_malloc(L, (l+1)*sizeof(char)+sizeof(TString))); ts->tsv.len = l; ts->tsv.hash = h; ts->tsv.marked = luaC_white(G(L)); ts->tsv.tt = LUA_TSTRING; ts->tsv.reserved = 0; memcpy(ts+1, str, l*sizeof(char)); ((char *)(ts+1))[l] = '\0'; /* ending 0 */ tb = &G(L)->strt; h = lmod(h, tb->size); ts->tsv.next = tb->hash[h]; /* chain new entry */ tb->hash[h] = obj2gco(ts); tb->nuse++; if (tb->nuse > cast(lu_int32, tb->size) && tb->size <= MAX_INT/2) luaS_resize(L, tb->size*2); /* too crowded */ return ts; } TString *luaS_newlstr (lua_State *L, const char *str, size_t l) { GCObject *o; unsigned int h = cast(unsigned int, l); /* seed */ size_t step = (l>>5)+1; /* if string is too long, don't hash all its chars */ size_t l1; for (l1=l; l1>=step; l1-=step) /* compute hash */ h = h ^ ((h<<5)+(h>>2)+cast(unsigned char, str[l1-1])); for (o = G(L)->strt.hash[lmod(h, G(L)->strt.size)]; o != NULL; o = o->gch.next) { TString *ts = rawgco2ts(o); if (ts->tsv.len == l && (memcmp(str, getstr(ts), l) == 0)) { /* string may be dead */ if (isdead(G(L), o)) changewhite(o); return ts; } } return newlstr(L, str, l, h); /* not found */ } Udata *luaS_newudata (lua_State *L, size_t s, Table *e) { Udata *u; if (s > MAX_SIZET - sizeof(Udata)) luaM_toobig(L); u = cast(Udata *, luaM_malloc(L, s + sizeof(Udata))); u->uv.marked = luaC_white(G(L)); /* is not finalized */ u->uv.tt = LUA_TUSERDATA; u->uv.len = s; u->uv.metatable = NULL; u->uv.env = e; /* chain it on udata list (after main thread) */ u->uv.next = G(L)->mainthread->next; G(L)->mainthread->next = obj2gco(u); return u; } ================================================ FILE: src/lstring.h ================================================ /* ** $Id: lstring.h,v 1.43.1.1 2007/12/27 13:02:25 roberto Exp $ ** String table (keep all strings handled by Lua) ** See Copyright Notice in lua.h */ #ifndef lstring_h #define lstring_h #include "lgc.h" #include "lobject.h" #include "lstate.h" #define sizestring(s) (sizeof(union TString)+((s)->len+1)*sizeof(char)) #define sizeudata(u) (sizeof(union Udata)+(u)->len) #define luaS_new(L, s) (luaS_newlstr(L, s, strlen(s))) #define luaS_newliteral(L, s) (luaS_newlstr(L, "" s, \ (sizeof(s)/sizeof(char))-1)) #define luaS_fix(s) l_setbit((s)->tsv.marked, FIXEDBIT) LUAI_FUNC void luaS_resize (lua_State *L, int newsize); LUAI_FUNC Udata *luaS_newudata (lua_State *L, size_t s, Table *e); LUAI_FUNC TString *luaS_newlstr (lua_State *L, const char *str, size_t l); #endif ================================================ FILE: src/lstrlib.c ================================================ /* ** $Id: lstrlib.c,v 1.132.1.5 2010/05/14 15:34:19 roberto Exp $ ** Standard library for string operations and pattern-matching ** See Copyright Notice in lua.h */ #include #include #include #include #include #define lstrlib_c #define LUA_LIB #include "lua.h" #include "lauxlib.h" #include "lualib.h" /* macro to `unsign' a character */ #define uchar(c) ((unsigned char)(c)) static int str_len (lua_State *L) { size_t l; luaL_checklstring(L, 1, &l); lua_pushinteger(L, l); return 1; } static ptrdiff_t posrelat (ptrdiff_t pos, size_t len) { /* relative string position: negative means back from end */ if (pos < 0) pos += (ptrdiff_t)len + 1; return (pos >= 0) ? pos : 0; } static int str_sub (lua_State *L) { size_t l; const char *s = luaL_checklstring(L, 1, &l); ptrdiff_t start = posrelat(luaL_checkinteger(L, 2), l); ptrdiff_t end = posrelat(luaL_optinteger(L, 3, -1), l); if (start < 1) start = 1; if (end > (ptrdiff_t)l) end = (ptrdiff_t)l; if (start <= end) lua_pushlstring(L, s+start-1, end-start+1); else lua_pushliteral(L, ""); return 1; } static int str_reverse (lua_State *L) { size_t l; luaL_Buffer b; const char *s = luaL_checklstring(L, 1, &l); luaL_buffinit(L, &b); while (l--) luaL_addchar(&b, s[l]); luaL_pushresult(&b); return 1; } static int str_lower (lua_State *L) { size_t l; size_t i; luaL_Buffer b; const char *s = luaL_checklstring(L, 1, &l); luaL_buffinit(L, &b); for (i=0; i 0) luaL_addlstring(&b, s, l); luaL_pushresult(&b); return 1; } static int str_byte (lua_State *L) { size_t l; const char *s = luaL_checklstring(L, 1, &l); ptrdiff_t posi = posrelat(luaL_optinteger(L, 2, 1), l); ptrdiff_t pose = posrelat(luaL_optinteger(L, 3, posi), l); int n, i; if (posi <= 0) posi = 1; if ((size_t)pose > l) pose = l; if (posi > pose) return 0; /* empty interval; return no values */ n = (int)(pose - posi + 1); if (posi + n <= pose) /* overflow? */ luaL_error(L, "string slice too long"); luaL_checkstack(L, n, "string slice too long"); for (i=0; i= ms->level || ms->capture[l].len == CAP_UNFINISHED) return luaL_error(ms->L, "invalid capture index"); return l; } static int capture_to_close (MatchState *ms) { int level = ms->level; for (level--; level>=0; level--) if (ms->capture[level].len == CAP_UNFINISHED) return level; return luaL_error(ms->L, "invalid pattern capture"); } static const char *classend (MatchState *ms, const char *p) { switch (*p++) { case L_ESC: { if (*p == '\0') luaL_error(ms->L, "malformed pattern (ends with " LUA_QL("%%") ")"); return p+1; } case '[': { if (*p == '^') p++; do { /* look for a `]' */ if (*p == '\0') luaL_error(ms->L, "malformed pattern (missing " LUA_QL("]") ")"); if (*(p++) == L_ESC && *p != '\0') p++; /* skip escapes (e.g. `%]') */ } while (*p != ']'); return p+1; } default: { return p; } } } static int match_class (int c, int cl) { int res; switch (tolower(cl)) { case 'a' : res = isalpha(c); break; case 'c' : res = iscntrl(c); break; case 'd' : res = isdigit(c); break; case 'l' : res = islower(c); break; case 'p' : res = ispunct(c); break; case 's' : res = isspace(c); break; case 'u' : res = isupper(c); break; case 'w' : res = isalnum(c); break; case 'x' : res = isxdigit(c); break; case 'z' : res = (c == 0); break; default: return (cl == c); } return (islower(cl) ? res : !res); } static int matchbracketclass (int c, const char *p, const char *ec) { int sig = 1; if (*(p+1) == '^') { sig = 0; p++; /* skip the `^' */ } while (++p < ec) { if (*p == L_ESC) { p++; if (match_class(c, uchar(*p))) return sig; } else if ((*(p+1) == '-') && (p+2 < ec)) { p+=2; if (uchar(*(p-2)) <= c && c <= uchar(*p)) return sig; } else if (uchar(*p) == c) return sig; } return !sig; } static int singlematch (int c, const char *p, const char *ep) { switch (*p) { case '.': return 1; /* matches any char */ case L_ESC: return match_class(c, uchar(*(p+1))); case '[': return matchbracketclass(c, p, ep-1); default: return (uchar(*p) == c); } } static const char *match (MatchState *ms, const char *s, const char *p); static const char *matchbalance (MatchState *ms, const char *s, const char *p) { if (*p == 0 || *(p+1) == 0) luaL_error(ms->L, "unbalanced pattern"); if (*s != *p) return NULL; else { int b = *p; int e = *(p+1); int cont = 1; while (++s < ms->src_end) { if (*s == e) { if (--cont == 0) return s+1; } else if (*s == b) cont++; } } return NULL; /* string ends out of balance */ } static const char *max_expand (MatchState *ms, const char *s, const char *p, const char *ep) { ptrdiff_t i = 0; /* counts maximum expand for item */ while ((s+i)src_end && singlematch(uchar(*(s+i)), p, ep)) i++; /* keeps trying to match with the maximum repetitions */ while (i>=0) { const char *res = match(ms, (s+i), ep+1); if (res) return res; i--; /* else didn't match; reduce 1 repetition to try again */ } return NULL; } static const char *min_expand (MatchState *ms, const char *s, const char *p, const char *ep) { for (;;) { const char *res = match(ms, s, ep+1); if (res != NULL) return res; else if (ssrc_end && singlematch(uchar(*s), p, ep)) s++; /* try with one more repetition */ else return NULL; } } static const char *start_capture (MatchState *ms, const char *s, const char *p, int what) { const char *res; int level = ms->level; if (level >= LUA_MAXCAPTURES) luaL_error(ms->L, "too many captures"); ms->capture[level].init = s; ms->capture[level].len = what; ms->level = level+1; if ((res=match(ms, s, p)) == NULL) /* match failed? */ ms->level--; /* undo capture */ return res; } static const char *end_capture (MatchState *ms, const char *s, const char *p) { int l = capture_to_close(ms); const char *res; ms->capture[l].len = s - ms->capture[l].init; /* close capture */ if ((res = match(ms, s, p)) == NULL) /* match failed? */ ms->capture[l].len = CAP_UNFINISHED; /* undo capture */ return res; } static const char *match_capture (MatchState *ms, const char *s, int l) { size_t len; l = check_capture(ms, l); len = ms->capture[l].len; if ((size_t)(ms->src_end-s) >= len && memcmp(ms->capture[l].init, s, len) == 0) return s+len; else return NULL; } static const char *match (MatchState *ms, const char *s, const char *p) { init: /* using goto's to optimize tail recursion */ switch (*p) { case '(': { /* start capture */ if (*(p+1) == ')') /* position capture? */ return start_capture(ms, s, p+2, CAP_POSITION); else return start_capture(ms, s, p+1, CAP_UNFINISHED); } case ')': { /* end capture */ return end_capture(ms, s, p+1); } case L_ESC: { switch (*(p+1)) { case 'b': { /* balanced string? */ s = matchbalance(ms, s, p+2); if (s == NULL) return NULL; p+=4; goto init; /* else return match(ms, s, p+4); */ } case 'f': { /* frontier? */ const char *ep; char previous; p += 2; if (*p != '[') luaL_error(ms->L, "missing " LUA_QL("[") " after " LUA_QL("%%f") " in pattern"); ep = classend(ms, p); /* points to what is next */ previous = (s == ms->src_init) ? '\0' : *(s-1); if (matchbracketclass(uchar(previous), p, ep-1) || !matchbracketclass(uchar(*s), p, ep-1)) return NULL; p=ep; goto init; /* else return match(ms, s, ep); */ } default: { if (isdigit(uchar(*(p+1)))) { /* capture results (%0-%9)? */ s = match_capture(ms, s, uchar(*(p+1))); if (s == NULL) return NULL; p+=2; goto init; /* else return match(ms, s, p+2) */ } goto dflt; /* case default */ } } } case '\0': { /* end of pattern */ return s; /* match succeeded */ } case '$': { if (*(p+1) == '\0') /* is the `$' the last char in pattern? */ return (s == ms->src_end) ? s : NULL; /* check end of string */ else goto dflt; } default: dflt: { /* it is a pattern item */ const char *ep = classend(ms, p); /* points to what is next */ int m = ssrc_end && singlematch(uchar(*s), p, ep); switch (*ep) { case '?': { /* optional */ const char *res; if (m && ((res=match(ms, s+1, ep+1)) != NULL)) return res; p=ep+1; goto init; /* else return match(ms, s, ep+1); */ } case '*': { /* 0 or more repetitions */ return max_expand(ms, s, p, ep); } case '+': { /* 1 or more repetitions */ return (m ? max_expand(ms, s+1, p, ep) : NULL); } case '-': { /* 0 or more repetitions (minimum) */ return min_expand(ms, s, p, ep); } default: { if (!m) return NULL; s++; p=ep; goto init; /* else return match(ms, s+1, ep); */ } } } } } static const char *lmemfind (const char *s1, size_t l1, const char *s2, size_t l2) { if (l2 == 0) return s1; /* empty strings are everywhere */ else if (l2 > l1) return NULL; /* avoids a negative `l1' */ else { const char *init; /* to search for a `*s2' inside `s1' */ l2--; /* 1st char will be checked by `memchr' */ l1 = l1-l2; /* `s2' cannot be found after that */ while (l1 > 0 && (init = (const char *)memchr(s1, *s2, l1)) != NULL) { init++; /* 1st char is already checked */ if (memcmp(init, s2+1, l2) == 0) return init-1; else { /* correct `l1' and `s1' to try again */ l1 -= init-s1; s1 = init; } } return NULL; /* not found */ } } static void push_onecapture (MatchState *ms, int i, const char *s, const char *e) { if (i >= ms->level) { if (i == 0) /* ms->level == 0, too */ lua_pushlstring(ms->L, s, e - s); /* add whole match */ else luaL_error(ms->L, "invalid capture index"); } else { ptrdiff_t l = ms->capture[i].len; if (l == CAP_UNFINISHED) luaL_error(ms->L, "unfinished capture"); if (l == CAP_POSITION) lua_pushinteger(ms->L, ms->capture[i].init - ms->src_init + 1); else lua_pushlstring(ms->L, ms->capture[i].init, l); } } static int push_captures (MatchState *ms, const char *s, const char *e) { int i; int nlevels = (ms->level == 0 && s) ? 1 : ms->level; luaL_checkstack(ms->L, nlevels, "too many captures"); for (i = 0; i < nlevels; i++) push_onecapture(ms, i, s, e); return nlevels; /* number of strings pushed */ } static int str_find_aux (lua_State *L, int find) { size_t l1, l2; const char *s = luaL_checklstring(L, 1, &l1); const char *p = luaL_checklstring(L, 2, &l2); ptrdiff_t init = posrelat(luaL_optinteger(L, 3, 1), l1) - 1; if (init < 0) init = 0; else if ((size_t)(init) > l1) init = (ptrdiff_t)l1; if (find && (lua_toboolean(L, 4) || /* explicit request? */ strpbrk(p, SPECIALS) == NULL)) { /* or no special characters? */ /* do a plain search */ const char *s2 = lmemfind(s+init, l1-init, p, l2); if (s2) { lua_pushinteger(L, s2-s+1); lua_pushinteger(L, s2-s+l2); return 2; } } else { MatchState ms; int anchor = (*p == '^') ? (p++, 1) : 0; const char *s1=s+init; ms.L = L; ms.src_init = s; ms.src_end = s+l1; do { const char *res; ms.level = 0; if ((res=match(&ms, s1, p)) != NULL) { if (find) { lua_pushinteger(L, s1-s+1); /* start */ lua_pushinteger(L, res-s); /* end */ return push_captures(&ms, NULL, 0) + 2; } else return push_captures(&ms, s1, res); } } while (s1++ < ms.src_end && !anchor); } lua_pushnil(L); /* not found */ return 1; } static int str_find (lua_State *L) { return str_find_aux(L, 1); } static int str_match (lua_State *L) { return str_find_aux(L, 0); } static int gmatch_aux (lua_State *L) { MatchState ms; size_t ls; const char *s = lua_tolstring(L, lua_upvalueindex(1), &ls); const char *p = lua_tostring(L, lua_upvalueindex(2)); const char *src; ms.L = L; ms.src_init = s; ms.src_end = s+ls; for (src = s + (size_t)lua_tointeger(L, lua_upvalueindex(3)); src <= ms.src_end; src++) { const char *e; ms.level = 0; if ((e = match(&ms, src, p)) != NULL) { lua_Integer newstart = e-s; if (e == src) newstart++; /* empty match? go at least one position */ lua_pushinteger(L, newstart); lua_replace(L, lua_upvalueindex(3)); return push_captures(&ms, src, e); } } return 0; /* not found */ } static int gmatch (lua_State *L) { luaL_checkstring(L, 1); luaL_checkstring(L, 2); lua_settop(L, 2); lua_pushinteger(L, 0); lua_pushcclosure(L, gmatch_aux, 3); return 1; } static int gfind_nodef (lua_State *L) { return luaL_error(L, LUA_QL("string.gfind") " was renamed to " LUA_QL("string.gmatch")); } static void add_s (MatchState *ms, luaL_Buffer *b, const char *s, const char *e) { size_t l, i; const char *news = lua_tolstring(ms->L, 3, &l); for (i = 0; i < l; i++) { if (news[i] != L_ESC) luaL_addchar(b, news[i]); else { i++; /* skip ESC */ if (!isdigit(uchar(news[i]))) luaL_addchar(b, news[i]); else if (news[i] == '0') luaL_addlstring(b, s, e - s); else { push_onecapture(ms, news[i] - '1', s, e); luaL_addvalue(b); /* add capture to accumulated result */ } } } } static void add_value (MatchState *ms, luaL_Buffer *b, const char *s, const char *e) { lua_State *L = ms->L; switch (lua_type(L, 3)) { case LUA_TNUMBER: case LUA_TSTRING: { add_s(ms, b, s, e); return; } case LUA_TFUNCTION: { int n; lua_pushvalue(L, 3); n = push_captures(ms, s, e); lua_call(L, n, 1); break; } case LUA_TTABLE: { push_onecapture(ms, 0, s, e); lua_gettable(L, 3); break; } } if (!lua_toboolean(L, -1)) { /* nil or false? */ lua_pop(L, 1); lua_pushlstring(L, s, e - s); /* keep original text */ } else if (!lua_isstring(L, -1)) luaL_error(L, "invalid replacement value (a %s)", luaL_typename(L, -1)); luaL_addvalue(b); /* add result to accumulator */ } static int str_gsub (lua_State *L) { size_t srcl; const char *src = luaL_checklstring(L, 1, &srcl); const char *p = luaL_checkstring(L, 2); int tr = lua_type(L, 3); int max_s = luaL_optint(L, 4, srcl+1); int anchor = (*p == '^') ? (p++, 1) : 0; int n = 0; MatchState ms; luaL_Buffer b; luaL_argcheck(L, tr == LUA_TNUMBER || tr == LUA_TSTRING || tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3, "string/function/table expected"); luaL_buffinit(L, &b); ms.L = L; ms.src_init = src; ms.src_end = src+srcl; while (n < max_s) { const char *e; ms.level = 0; e = match(&ms, src, p); if (e) { n++; add_value(&ms, &b, src, e); } if (e && e>src) /* non empty match? */ src = e; /* skip it */ else if (src < ms.src_end) luaL_addchar(&b, *src++); else break; if (anchor) break; } luaL_addlstring(&b, src, ms.src_end-src); luaL_pushresult(&b); lua_pushinteger(L, n); /* number of substitutions */ return 2; } /* }====================================================== */ /* maximum size of each formatted item (> len(format('%99.99f', -1e308))) */ #define MAX_ITEM 512 /* valid flags in a format specification */ #define FLAGS "-+ #0" /* ** maximum size of each format specification (such as '%-099.99d') ** (+10 accounts for %99.99x plus margin of error) */ #define MAX_FORMAT (sizeof(FLAGS) + sizeof(LUA_INTFRMLEN) + 10) static void addquoted (lua_State *L, luaL_Buffer *b, int arg) { size_t l; const char *s = luaL_checklstring(L, arg, &l); luaL_addchar(b, '"'); while (l--) { switch (*s) { case '"': case '\\': case '\n': { luaL_addchar(b, '\\'); luaL_addchar(b, *s); break; } case '\r': { luaL_addlstring(b, "\\r", 2); break; } case '\0': { luaL_addlstring(b, "\\000", 4); break; } default: { luaL_addchar(b, *s); break; } } s++; } luaL_addchar(b, '"'); } static const char *scanformat (lua_State *L, const char *strfrmt, char *form) { const char *p = strfrmt; while (*p != '\0' && strchr(FLAGS, *p) != NULL) p++; /* skip flags */ if ((size_t)(p - strfrmt) >= sizeof(FLAGS)) luaL_error(L, "invalid format (repeated flags)"); if (isdigit(uchar(*p))) p++; /* skip width */ if (isdigit(uchar(*p))) p++; /* (2 digits at most) */ if (*p == '.') { p++; if (isdigit(uchar(*p))) p++; /* skip precision */ if (isdigit(uchar(*p))) p++; /* (2 digits at most) */ } if (isdigit(uchar(*p))) luaL_error(L, "invalid format (width or precision too long)"); *(form++) = '%'; strncpy(form, strfrmt, p - strfrmt + 1); form += p - strfrmt + 1; *form = '\0'; return p; } static void addintlen (char *form) { size_t l = strlen(form); char spec = form[l - 1]; strcpy(form + l - 1, LUA_INTFRMLEN); form[l + sizeof(LUA_INTFRMLEN) - 2] = spec; form[l + sizeof(LUA_INTFRMLEN) - 1] = '\0'; } static int str_format (lua_State *L) { int top = lua_gettop(L); int arg = 1; size_t sfl; const char *strfrmt = luaL_checklstring(L, arg, &sfl); const char *strfrmt_end = strfrmt+sfl; luaL_Buffer b; luaL_buffinit(L, &b); while (strfrmt < strfrmt_end) { if (*strfrmt != L_ESC) luaL_addchar(&b, *strfrmt++); else if (*++strfrmt == L_ESC) luaL_addchar(&b, *strfrmt++); /* %% */ else { /* format item */ char form[MAX_FORMAT]; /* to store the format (`%...') */ char buff[MAX_ITEM]; /* to store the formatted item */ if (++arg > top) luaL_argerror(L, arg, "no value"); strfrmt = scanformat(L, strfrmt, form); switch (*strfrmt++) { case 'c': { sprintf(buff, form, (int)luaL_checknumber(L, arg)); break; } case 'd': case 'i': { addintlen(form); sprintf(buff, form, (LUA_INTFRM_T)luaL_checknumber(L, arg)); break; } case 'o': case 'u': case 'x': case 'X': { addintlen(form); sprintf(buff, form, (unsigned LUA_INTFRM_T)luaL_checknumber(L, arg)); break; } case 'e': case 'E': case 'f': case 'g': case 'G': { sprintf(buff, form, (double)luaL_checknumber(L, arg)); break; } case 'q': { addquoted(L, &b, arg); continue; /* skip the 'addsize' at the end */ } case 's': { size_t l; const char *s = luaL_checklstring(L, arg, &l); if (!strchr(form, '.') && l >= 100) { /* no precision and string is too long to be formatted; keep original string */ lua_pushvalue(L, arg); luaL_addvalue(&b); continue; /* skip the `addsize' at the end */ } else { sprintf(buff, form, s); break; } } default: { /* also treat cases `pnLlh' */ return luaL_error(L, "invalid option " LUA_QL("%%%c") " to " LUA_QL("format"), *(strfrmt - 1)); } } luaL_addlstring(&b, buff, strlen(buff)); } } luaL_pushresult(&b); return 1; } static const luaL_Reg strlib[] = { {"byte", str_byte}, {"char", str_char}, /* no 'dump' without sandboxing 'loadstring', etc. */ {"find", str_find}, {"format", str_format}, {"gfind", gfind_nodef}, {"gmatch", gmatch}, {"gsub", str_gsub}, {"len", str_len}, {"lower", str_lower}, {"match", str_match}, {"rep", str_rep}, {"reverse", str_reverse}, {"sub", str_sub}, {"upper", str_upper}, {NULL, NULL} }; static void createmetatable (lua_State *L) { lua_createtable(L, 0, 1); /* create metatable for strings */ lua_pushliteral(L, ""); /* dummy string */ lua_pushvalue(L, -2); lua_setmetatable(L, -2); /* set string metatable */ lua_pop(L, 1); /* pop dummy string */ lua_pushvalue(L, -2); /* string library... */ lua_setfield(L, -2, "__index"); /* ...is the __index metamethod */ lua_pop(L, 1); /* pop metatable */ } /* ** Open string library */ LUALIB_API int luaopen_string (lua_State *L) { luaL_register(L, LUA_STRLIBNAME, strlib); #if defined(LUA_COMPAT_GFIND) lua_getfield(L, -1, "gmatch"); lua_setfield(L, -2, "gfind"); #endif createmetatable(L); return 1; } ================================================ FILE: src/ltable.c ================================================ /* ** $Id: ltable.c,v 2.32.1.2 2007/12/28 15:32:23 roberto Exp $ ** Lua tables (hash) ** See Copyright Notice in lua.h */ /* ** Implementation of tables (aka arrays, objects, or hash tables). ** Tables keep its elements in two parts: an array part and a hash part. ** Non-negative integer keys are all candidates to be kept in the array ** part. The actual size of the array is the largest `n' such that at ** least half the slots between 0 and n are in use. ** Hash uses a mix of chained scatter table with Brent's variation. ** A main invariant of these tables is that, if an element is not ** in its main position (i.e. the `original' position that its hash gives ** to it), then the colliding element is in its own main position. ** Hence even when the load factor reaches 100%, performance remains good. */ #include #include #define ltable_c #define LUA_CORE #include "lua.h" #include "ldebug.h" #include "ldo.h" #include "lgc.h" #include "lmem.h" #include "lobject.h" #include "lstate.h" #include "ltable.h" /* ** max size of array part is 2^MAXBITS */ #if LUAI_BITSINT > 26 #define MAXBITS 26 #else #define MAXBITS (LUAI_BITSINT-2) #endif #define MAXASIZE (1 << MAXBITS) #define hashpow2(t,n) (gnode(t, lmod((n), sizenode(t)))) #define hashstr(t,str) hashpow2(t, (str)->tsv.hash) #define hashboolean(t,p) hashpow2(t, p) /* ** for some types, it is better to avoid modulus by power of 2, as ** they tend to have many 2 factors. */ #define hashmod(t,n) (gnode(t, ((n) % ((sizenode(t)-1)|1)))) #define hashpointer(t,p) hashmod(t, IntPoint(p)) /* ** number of ints inside a lua_Number */ #define numints cast_int(sizeof(lua_Number)/sizeof(int)) #define dummynode (&dummynode_) static const Node dummynode_ = { {{NULL}, LUA_TNIL}, /* value */ {{{NULL}, LUA_TNIL, NULL}} /* key */ }; /* ** hash for lua_Numbers */ static Node *hashnum (const Table *t, lua_Number n) { unsigned int a[numints]; int i; if (luai_numeq(n, 0)) /* avoid problems with -0 */ return gnode(t, 0); memcpy(a, &n, sizeof(a)); for (i = 1; i < numints; i++) a[0] += a[i]; return hashmod(t, a[0]); } /* ** returns the `main' position of an element in a table (that is, the index ** of its hash value) */ static Node *mainposition (const Table *t, const TValue *key) { switch (ttype(key)) { case LUA_TNUMBER: return hashnum(t, nvalue(key)); case LUA_TSTRING: return hashstr(t, rawtsvalue(key)); case LUA_TBOOLEAN: return hashboolean(t, bvalue(key)); case LUA_TLIGHTUSERDATA: return hashpointer(t, pvalue(key)); default: return hashpointer(t, gcvalue(key)); } } /* ** returns the index for `key' if `key' is an appropriate key to live in ** the array part of the table, -1 otherwise. */ static int arrayindex (const TValue *key) { if (ttisnumber(key)) { lua_Number n = nvalue(key); int k; lua_number2int(k, n); if (luai_numeq(cast_num(k), n)) return k; } return -1; /* `key' did not match some condition */ } /* ** returns the index of a `key' for table traversals. First goes all ** elements in the array part, then elements in the hash part. The ** beginning of a traversal is signalled by -1. */ static int findindex (lua_State *L, Table *t, StkId key) { int i; if (ttisnil(key)) return -1; /* first iteration */ i = arrayindex(key); if (0 < i && i <= t->sizearray) /* is `key' inside array part? */ return i-1; /* yes; that's the index (corrected to C) */ else { Node *n = mainposition(t, key); do { /* check whether `key' is somewhere in the chain */ /* key may be dead already, but it is ok to use it in `next' */ if (luaO_rawequalObj(key2tval(n), key) || (ttype(gkey(n)) == LUA_TDEADKEY && iscollectable(key) && gcvalue(gkey(n)) == gcvalue(key))) { i = cast_int(n - gnode(t, 0)); /* key index in hash table */ /* hash elements are numbered after array ones */ return i + t->sizearray; } else n = gnext(n); } while (n); luaG_runerror(L, "invalid key to " LUA_QL("next")); /* key not found */ return 0; /* to avoid warnings */ } } int luaH_next (lua_State *L, Table *t, StkId key) { int i = findindex(L, t, key); /* find original element */ for (i++; i < t->sizearray; i++) { /* try first array part */ if (!ttisnil(&t->array[i])) { /* a non-nil value? */ setnvalue(key, cast_num(i+1)); setobj2s(L, key+1, &t->array[i]); return 1; } } for (i -= t->sizearray; i < sizenode(t); i++) { /* then hash part */ if (!ttisnil(gval(gnode(t, i)))) { /* a non-nil value? */ setobj2s(L, key, key2tval(gnode(t, i))); setobj2s(L, key+1, gval(gnode(t, i))); return 1; } } return 0; /* no more elements */ } /* ** {============================================================= ** Rehash ** ============================================================== */ static int computesizes (int nums[], int *narray) { int i; int twotoi; /* 2^i */ int a = 0; /* number of elements smaller than 2^i */ int na = 0; /* number of elements to go to array part */ int n = 0; /* optimal size for array part */ for (i = 0, twotoi = 1; twotoi/2 < *narray; i++, twotoi *= 2) { if (nums[i] > 0) { a += nums[i]; if (a > twotoi/2) { /* more than half elements present? */ n = twotoi; /* optimal size (till now) */ na = a; /* all elements smaller than n will go to array part */ } } if (a == *narray) break; /* all elements already counted */ } *narray = n; lua_assert(*narray/2 <= na && na <= *narray); return na; } static int countint (const TValue *key, int *nums) { int k = arrayindex(key); if (0 < k && k <= MAXASIZE) { /* is `key' an appropriate array index? */ nums[ceillog2(k)]++; /* count as such */ return 1; } else return 0; } static int numusearray (const Table *t, int *nums) { int lg; int ttlg; /* 2^lg */ int ause = 0; /* summation of `nums' */ int i = 1; /* count to traverse all array keys */ for (lg=0, ttlg=1; lg<=MAXBITS; lg++, ttlg*=2) { /* for each slice */ int lc = 0; /* counter */ int lim = ttlg; if (lim > t->sizearray) { lim = t->sizearray; /* adjust upper limit */ if (i > lim) break; /* no more elements to count */ } /* count elements in range (2^(lg-1), 2^lg] */ for (; i <= lim; i++) { if (!ttisnil(&t->array[i-1])) lc++; } nums[lg] += lc; ause += lc; } return ause; } static int numusehash (const Table *t, int *nums, int *pnasize) { int totaluse = 0; /* total number of elements */ int ause = 0; /* summation of `nums' */ int i = sizenode(t); while (i--) { Node *n = &t->node[i]; if (!ttisnil(gval(n))) { ause += countint(key2tval(n), nums); totaluse++; } } *pnasize += ause; return totaluse; } static void setarrayvector (lua_State *L, Table *t, int size) { int i; luaM_reallocvector(L, t->array, t->sizearray, size, TValue); for (i=t->sizearray; iarray[i]); t->sizearray = size; } static void setnodevector (lua_State *L, Table *t, int size) { int lsize; if (size == 0) { /* no elements to hash part? */ t->node = cast(Node *, dummynode); /* use common `dummynode' */ lsize = 0; } else { int i; lsize = ceillog2(size); if (lsize > MAXBITS) luaG_runerror(L, "table overflow"); size = twoto(lsize); t->node = luaM_newvector(L, size, Node); for (i=0; ilsizenode = cast_byte(lsize); t->lastfree = gnode(t, size); /* all positions are free */ } static void resize (lua_State *L, Table *t, int nasize, int nhsize) { int i; int oldasize = t->sizearray; int oldhsize = t->lsizenode; Node *nold = t->node; /* save old hash ... */ if (nasize > oldasize) /* array part must grow? */ setarrayvector(L, t, nasize); /* create new hash part with appropriate size */ setnodevector(L, t, nhsize); if (nasize < oldasize) { /* array part must shrink? */ t->sizearray = nasize; /* re-insert elements from vanishing slice */ for (i=nasize; iarray[i])) setobjt2t(L, luaH_setnum(L, t, i+1), &t->array[i]); } /* shrink array */ luaM_reallocvector(L, t->array, oldasize, nasize, TValue); } /* re-insert elements from hash part */ for (i = twoto(oldhsize) - 1; i >= 0; i--) { Node *old = nold+i; if (!ttisnil(gval(old))) setobjt2t(L, luaH_set(L, t, key2tval(old)), gval(old)); } if (nold != dummynode) luaM_freearray(L, nold, twoto(oldhsize), Node); /* free old array */ } void luaH_resizearray (lua_State *L, Table *t, int nasize) { int nsize = (t->node == dummynode) ? 0 : sizenode(t); resize(L, t, nasize, nsize); } static void rehash (lua_State *L, Table *t, const TValue *ek) { int nasize, na; int nums[MAXBITS+1]; /* nums[i] = number of keys between 2^(i-1) and 2^i */ int i; int totaluse; for (i=0; i<=MAXBITS; i++) nums[i] = 0; /* reset counts */ nasize = numusearray(t, nums); /* count keys in array part */ totaluse = nasize; /* all those keys are integer keys */ totaluse += numusehash(t, nums, &nasize); /* count keys in hash part */ /* count extra key */ nasize += countint(ek, nums); totaluse++; /* compute new size for array part */ na = computesizes(nums, &nasize); /* resize the table to new computed sizes */ resize(L, t, nasize, totaluse - na); } /* ** }============================================================= */ Table *luaH_new (lua_State *L, int narray, int nhash) { Table *t = luaM_new(L, Table); luaC_link(L, obj2gco(t), LUA_TTABLE); t->metatable = NULL; t->flags = cast_byte(~0); /* temporary values (kept only if some malloc fails) */ t->array = NULL; t->sizearray = 0; t->lsizenode = 0; t->node = cast(Node *, dummynode); setarrayvector(L, t, narray); setnodevector(L, t, nhash); return t; } void luaH_free (lua_State *L, Table *t) { if (t->node != dummynode) luaM_freearray(L, t->node, sizenode(t), Node); luaM_freearray(L, t->array, t->sizearray, TValue); luaM_free(L, t); } static Node *getfreepos (Table *t) { while (t->lastfree-- > t->node) { if (ttisnil(gkey(t->lastfree))) return t->lastfree; } return NULL; /* could not find a free place */ } /* ** inserts a new key into a hash table; first, check whether key's main ** position is free. If not, check whether colliding node is in its main ** position or not: if it is not, move colliding node to an empty place and ** put new key in its main position; otherwise (colliding node is in its main ** position), new key goes to an empty position. */ static TValue *newkey (lua_State *L, Table *t, const TValue *key) { Node *mp = mainposition(t, key); if (!ttisnil(gval(mp)) || mp == dummynode) { Node *othern; Node *n = getfreepos(t); /* get a free place */ if (n == NULL) { /* cannot find a free place? */ rehash(L, t, key); /* grow table */ return luaH_set(L, t, key); /* re-insert key into grown table */ } lua_assert(n != dummynode); othern = mainposition(t, key2tval(mp)); if (othern != mp) { /* is colliding node out of its main position? */ /* yes; move colliding node into free position */ while (gnext(othern) != mp) othern = gnext(othern); /* find previous */ gnext(othern) = n; /* redo the chain with `n' in place of `mp' */ *n = *mp; /* copy colliding node into free pos. (mp->next also goes) */ gnext(mp) = NULL; /* now `mp' is free */ setnilvalue(gval(mp)); } else { /* colliding node is in its own main position */ /* new node will go into free position */ gnext(n) = gnext(mp); /* chain new position */ gnext(mp) = n; mp = n; } } gkey(mp)->value = key->value; gkey(mp)->tt = key->tt; luaC_barriert(L, t, key); lua_assert(ttisnil(gval(mp))); return gval(mp); } /* ** search function for integers */ const TValue *luaH_getnum (Table *t, int key) { /* (1 <= key && key <= t->sizearray) */ if (cast(unsigned int, key-1) < cast(unsigned int, t->sizearray)) return &t->array[key-1]; else { lua_Number nk = cast_num(key); Node *n = hashnum(t, nk); do { /* check whether `key' is somewhere in the chain */ if (ttisnumber(gkey(n)) && luai_numeq(nvalue(gkey(n)), nk)) return gval(n); /* that's it */ else n = gnext(n); } while (n); return luaO_nilobject; } } /* ** search function for strings */ const TValue *luaH_getstr (Table *t, TString *key) { Node *n = hashstr(t, key); do { /* check whether `key' is somewhere in the chain */ if (ttisstring(gkey(n)) && rawtsvalue(gkey(n)) == key) return gval(n); /* that's it */ else n = gnext(n); } while (n); return luaO_nilobject; } /* ** main search function */ const TValue *luaH_get (Table *t, const TValue *key) { switch (ttype(key)) { case LUA_TNIL: return luaO_nilobject; case LUA_TSTRING: return luaH_getstr(t, rawtsvalue(key)); case LUA_TNUMBER: { int k; lua_Number n = nvalue(key); lua_number2int(k, n); if (luai_numeq(cast_num(k), nvalue(key))) /* index is int? */ return luaH_getnum(t, k); /* use specialized version */ /* else go through */ } default: { Node *n = mainposition(t, key); do { /* check whether `key' is somewhere in the chain */ if (luaO_rawequalObj(key2tval(n), key)) return gval(n); /* that's it */ else n = gnext(n); } while (n); return luaO_nilobject; } } } TValue *luaH_set (lua_State *L, Table *t, const TValue *key) { const TValue *p = luaH_get(t, key); t->flags = 0; if (p != luaO_nilobject) return cast(TValue *, p); else { if (ttisnil(key)) luaG_runerror(L, "table index is nil"); else if (ttisnumber(key) && luai_numisnan(nvalue(key))) luaG_runerror(L, "table index is NaN"); return newkey(L, t, key); } } TValue *luaH_setnum (lua_State *L, Table *t, int key) { const TValue *p = luaH_getnum(t, key); if (p != luaO_nilobject) return cast(TValue *, p); else { TValue k; setnvalue(&k, cast_num(key)); return newkey(L, t, &k); } } TValue *luaH_setstr (lua_State *L, Table *t, TString *key) { const TValue *p = luaH_getstr(t, key); if (p != luaO_nilobject) return cast(TValue *, p); else { TValue k; setsvalue(L, &k, key); return newkey(L, t, &k); } } static int unbound_search (Table *t, unsigned int j) { unsigned int i = j; /* i is zero or a present index */ j++; /* find `i' and `j' such that i is present and j is not */ while (!ttisnil(luaH_getnum(t, j))) { i = j; j *= 2; if (j > cast(unsigned int, MAX_INT)) { /* overflow? */ /* table was built with bad purposes: resort to linear search */ i = 1; while (!ttisnil(luaH_getnum(t, i))) i++; return i - 1; } } /* now do a binary search between them */ while (j - i > 1) { unsigned int m = (i+j)/2; if (ttisnil(luaH_getnum(t, m))) j = m; else i = m; } return i; } /* ** Try to find a boundary in table `t'. A `boundary' is an integer index ** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil). */ int luaH_getn (Table *t) { unsigned int j = t->sizearray; if (j > 0 && ttisnil(&t->array[j - 1])) { /* there is a boundary in the array part: (binary) search for it */ unsigned int i = 0; while (j - i > 1) { unsigned int m = (i+j)/2; if (ttisnil(&t->array[m - 1])) j = m; else i = m; } return i; } /* else must find a boundary in hash part */ else if (t->node == dummynode) /* hash part is empty? */ return j; /* that is easy... */ else return unbound_search(t, j); } #if defined(LUA_DEBUG) Node *luaH_mainposition (const Table *t, const TValue *key) { return mainposition(t, key); } int luaH_isdummy (Node *n) { return n == dummynode; } #endif ================================================ FILE: src/ltable.h ================================================ /* ** $Id: ltable.h,v 2.10.1.1 2007/12/27 13:02:25 roberto Exp $ ** Lua tables (hash) ** See Copyright Notice in lua.h */ #ifndef ltable_h #define ltable_h #include "lobject.h" #define gnode(t,i) (&(t)->node[i]) #define gkey(n) (&(n)->i_key.nk) #define gval(n) (&(n)->i_val) #define gnext(n) ((n)->i_key.nk.next) #define key2tval(n) (&(n)->i_key.tvk) LUAI_FUNC const TValue *luaH_getnum (Table *t, int key); LUAI_FUNC TValue *luaH_setnum (lua_State *L, Table *t, int key); LUAI_FUNC const TValue *luaH_getstr (Table *t, TString *key); LUAI_FUNC TValue *luaH_setstr (lua_State *L, Table *t, TString *key); LUAI_FUNC const TValue *luaH_get (Table *t, const TValue *key); LUAI_FUNC TValue *luaH_set (lua_State *L, Table *t, const TValue *key); LUAI_FUNC Table *luaH_new (lua_State *L, int narray, int lnhash); LUAI_FUNC void luaH_resizearray (lua_State *L, Table *t, int nasize); LUAI_FUNC void luaH_free (lua_State *L, Table *t); LUAI_FUNC int luaH_next (lua_State *L, Table *t, StkId key); LUAI_FUNC int luaH_getn (Table *t); #if defined(LUA_DEBUG) LUAI_FUNC Node *luaH_mainposition (const Table *t, const TValue *key); LUAI_FUNC int luaH_isdummy (Node *n); #endif #endif ================================================ FILE: src/ltablib.c ================================================ /* ** $Id: ltablib.c,v 1.38.1.3 2008/02/14 16:46:58 roberto Exp $ ** Library for Table Manipulation ** See Copyright Notice in lua.h */ #include #define ltablib_c #define LUA_LIB #include "lua.h" #include "lauxlib.h" #include "lualib.h" #define aux_getn(L,n) (luaL_checktype(L, n, LUA_TTABLE), luaL_getn(L, n)) static int foreachi (lua_State *L) { int i; int n = aux_getn(L, 1); luaL_checktype(L, 2, LUA_TFUNCTION); for (i=1; i <= n; i++) { lua_pushvalue(L, 2); /* function */ lua_pushinteger(L, i); /* 1st argument */ lua_rawgeti(L, 1, i); /* 2nd argument */ lua_call(L, 2, 1); if (!lua_isnil(L, -1)) return 1; lua_pop(L, 1); /* remove nil result */ } return 0; } static int foreach (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); luaL_checktype(L, 2, LUA_TFUNCTION); lua_pushnil(L); /* first key */ while (lua_next(L, 1)) { lua_pushvalue(L, 2); /* function */ lua_pushvalue(L, -3); /* key */ lua_pushvalue(L, -3); /* value */ lua_call(L, 2, 1); if (!lua_isnil(L, -1)) return 1; lua_pop(L, 2); /* remove value and result */ } return 0; } static int maxn (lua_State *L) { lua_Number max = 0; luaL_checktype(L, 1, LUA_TTABLE); lua_pushnil(L); /* first key */ while (lua_next(L, 1)) { lua_pop(L, 1); /* remove value */ if (lua_type(L, -1) == LUA_TNUMBER) { lua_Number v = lua_tonumber(L, -1); if (v > max) max = v; } } lua_pushnumber(L, max); return 1; } static int getn (lua_State *L) { lua_pushinteger(L, aux_getn(L, 1)); return 1; } static int setn (lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); #ifndef luaL_setn luaL_setn(L, 1, luaL_checkint(L, 2)); #else luaL_error(L, LUA_QL("setn") " is obsolete"); #endif lua_pushvalue(L, 1); return 1; } static int tinsert (lua_State *L) { int e = aux_getn(L, 1) + 1; /* first empty element */ int pos; /* where to insert new element */ switch (lua_gettop(L)) { case 2: { /* called with only 2 arguments */ pos = e; /* insert new element at the end */ break; } case 3: { int i; pos = luaL_checkint(L, 2); /* 2nd argument is the position */ if (pos > e) e = pos; /* `grow' array if necessary */ for (i = e; i > pos; i--) { /* move up elements */ lua_rawgeti(L, 1, i-1); lua_rawseti(L, 1, i); /* t[i] = t[i-1] */ } break; } default: { return luaL_error(L, "wrong number of arguments to " LUA_QL("insert")); } } luaL_setn(L, 1, e); /* new size */ lua_rawseti(L, 1, pos); /* t[pos] = v */ return 0; } static int tremove (lua_State *L) { int e = aux_getn(L, 1); int pos = luaL_optint(L, 2, e); if (!(1 <= pos && pos <= e)) /* position is outside bounds? */ return 0; /* nothing to remove */ luaL_setn(L, 1, e - 1); /* t.n = n-1 */ lua_rawgeti(L, 1, pos); /* result = t[pos] */ for ( ;pos= P */ while (lua_rawgeti(L, 1, ++i), sort_comp(L, -1, -2)) { if (i>u) luaL_error(L, "invalid order function for sorting"); lua_pop(L, 1); /* remove a[i] */ } /* repeat --j until a[j] <= P */ while (lua_rawgeti(L, 1, --j), sort_comp(L, -3, -1)) { if (j #define ltm_c #define LUA_CORE #include "lua.h" #include "lobject.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "ltm.h" const char *const luaT_typenames[] = { "nil", "boolean", "userdata", "number", "string", "table", "function", "userdata", "thread", "proto", "upval" }; void luaT_init (lua_State *L) { static const char *const luaT_eventname[] = { /* ORDER TM */ "__index", "__newindex", "__gc", "__mode", "__eq", "__add", "__sub", "__mul", "__div", "__mod", "__pow", "__unm", "__len", "__lt", "__le", "__concat", "__call" }; int i; for (i=0; itmname[i] = luaS_new(L, luaT_eventname[i]); luaS_fix(G(L)->tmname[i]); /* never collect these names */ } } /* ** function to be used with macro "fasttm": optimized for absence of ** tag methods */ const TValue *luaT_gettm (Table *events, TMS event, TString *ename) { const TValue *tm = luaH_getstr(events, ename); lua_assert(event <= TM_EQ); if (ttisnil(tm)) { /* no tag method? */ events->flags |= cast_byte(1u<metatable; break; case LUA_TUSERDATA: mt = uvalue(o)->metatable; break; default: mt = G(L)->mt[ttype(o)]; } return (mt ? luaH_getstr(mt, G(L)->tmname[event]) : luaO_nilobject); } ================================================ FILE: src/ltm.h ================================================ /* ** $Id: ltm.h,v 2.6.1.1 2007/12/27 13:02:25 roberto Exp $ ** Tag methods ** See Copyright Notice in lua.h */ #ifndef ltm_h #define ltm_h #include "lobject.h" /* * WARNING: if you change the order of this enumeration, * grep "ORDER TM" */ typedef enum { TM_INDEX, TM_NEWINDEX, TM_GC, TM_MODE, TM_EQ, /* last tag method with `fast' access */ TM_ADD, TM_SUB, TM_MUL, TM_DIV, TM_MOD, TM_POW, TM_UNM, TM_LEN, TM_LT, TM_LE, TM_CONCAT, TM_CALL, TM_N /* number of elements in the enum */ } TMS; #define gfasttm(g,et,e) ((et) == NULL ? NULL : \ ((et)->flags & (1u<<(e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e])) #define fasttm(l,et,e) gfasttm(G(l), et, e) LUAI_DATA const char *const luaT_typenames[]; LUAI_FUNC const TValue *luaT_gettm (Table *events, TMS event, TString *ename); LUAI_FUNC const TValue *luaT_gettmbyobj (lua_State *L, const TValue *o, TMS event); LUAI_FUNC void luaT_init (lua_State *L); #endif ================================================ FILE: src/lua.c ================================================ /* ** $Id: lua.c,v 1.160.1.2 2007/12/28 15:32:23 roberto Exp $ ** Lua stand-alone interpreter ** See Copyright Notice in lua.h */ #include #ifdef __NetBSD__ #include #else #include #endif #include #include #include #include #define lua_c #include "lua.h" #include "teliva.h" #include "lauxlib.h" #include "lualib.h" static lua_State *globalL = NULL; static const char *progname = LUA_PROGNAME; static void lstop (lua_State *L, lua_Debug *ar) { (void)ar; /* unused arg. */ lua_sethook(L, NULL, 0, 0); luaL_error(L, "interrupted!"); } static void laction (int i) { signal(i, SIG_DFL); /* if another SIGINT happens before lstop, terminate process (default action) */ lua_sethook(globalL, lstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1); } static void print_usage (void) { printf("usage: %s ___.tlv [args]\n", progname); } static void l_message (const char *pname, const char *msg) { if (!stdscr || isendwin()) { printf("%s: %s\n", pname, msg); exit(1); } if (pname) mvprintw(LINES-2, 0, "%s: ", pname); printw(msg); mvprintw(LINES-1, 0, "sorry, you'll need to edit the image directly. press any key to exit."); refresh(); nodelay(stdscr, 0); /* make getch() block */ getch(); } static int report (lua_State *L, int status) { if (status && !lua_isnil(L, -1)) { const char *msg = lua_tostring(L, -1); if (msg == NULL) msg = "(error object is not a string)"; l_message(progname, msg); lua_pop(L, 1); } return status; } static int traceback (lua_State *L) { if (!lua_isstring(L, 1)) /* 'message' not a string? */ return 1; /* keep it intact */ lua_getfield(L, LUA_GLOBALSINDEX, "debug"); if (!lua_istable(L, -1)) { lua_pop(L, 1); return 1; } lua_getfield(L, -1, "traceback"); if (!lua_isfunction(L, -1)) { lua_pop(L, 2); return 1; } lua_pushvalue(L, 1); /* pass error message */ lua_pushinteger(L, 2); /* skip this function and traceback */ lua_call(L, 2, 1); /* call debug.traceback */ return 1; } int docall (lua_State *L, int narg, int clear) { int status; int base = lua_gettop(L) - narg; /* function index */ lua_pushcfunction(L, traceback); /* push traceback function */ lua_insert(L, base); /* put it under chunk and args */ signal(SIGINT, laction); status = lua_pcall(L, narg, (clear ? 0 : LUA_MULTRET), base); signal(SIGINT, SIG_DFL); lua_remove(L, base); /* remove traceback function */ /* force a complete garbage collection in case of errors */ if (status != 0) lua_gc(L, LUA_GCCOLLECT, 0); return status; } /* initialize global binding "args" for commandline args */ void set_args (lua_State *L, char **argv, int n) { int narg; int i; int argc = 0; while (argv[argc]) argc++; /* count total number of arguments */ narg = argc - (n + 1); /* number of arguments to the script */ luaL_checkstack(L, narg + 3, "too many arguments to script"); lua_newtable(L); for (i=0; i < argc; i++) { lua_pushstring(L, argv[i]); lua_rawseti(L, -2, i - n); } lua_setglobal(L, "arg"); } static int dofile (lua_State *L, const char *name) { int status = luaL_loadfile(L, name) || docall(L, 0, 1); return report_in_developer_mode(L, status); } int dostring (lua_State *L, const char *s, const char *name) { int status = luaL_loadbuffer(L, s, strlen(s), name) || docall(L, 0, 1); return report_in_developer_mode(L, status); } void stack_dump (lua_State *L) { int i; int top = lua_gettop(L); int y = 1; for (i = 1; i <= top; i++) { /* repeat for each level */ int t = lua_type(L, i); switch (t) { case LUA_TSTRING: /* strings */ mvprintw(y, 30, "`%s'", lua_tostring(L, i)); break; case LUA_TBOOLEAN: /* booleans */ mvprintw(y, 30, lua_toboolean(L, i) ? "true" : "false"); break; case LUA_TNUMBER: /* numbers */ mvprintw(y, 30, "%g", lua_tonumber(L, i)); break; default: /* other values */ mvprintw(y, 30, "%s", lua_typename(L, t)); break; } y++; mvprintw(y, 30, " "); /* put a separator */ y++; } mvprintw(y, 30, "\n"); /* end the listing */ y++; } static int handle_luainit (lua_State *L) { const char *init = getenv(LUA_INIT); if (init == NULL) return 0; /* status OK */ else if (init[0] == '@') return dofile(L, init+1); else return dostring(L, init, "=" LUA_INIT); } /* roughly equivalent to: * globalname = require(filename) */ static int dorequire (lua_State *L, const char *filename, const char *globalname) { int status = luaL_loadfile(L, filename) || docall(L, /*nargs*/0, /*don't clean up stack*/0); if (status != 0) return report_in_developer_mode(L, status); if (lua_isnil(L, -1)) { endwin(); printf("%s didn't return a module\n", filename); exit(1); } lua_setglobal(L, globalname); return 0; } struct Smain { int argc; char **argv; int status; }; /* does its own error handling, always returns 0 to prevent duplicate messages */ static int pmain (lua_State *L) { struct Smain *s = (struct Smain *)lua_touserdata(L, 1); char **argv = s->argv; int status; globalL = L; if (argv[0] && argv[0][0]) progname = argv[0]; lua_gc(L, LUA_GCSTOP, 0); /* stop collector during initialization */ luaL_openlibs(L); status = dorequire(L, "src/lcurses/curses.lua", "curses"); if (status != 0) return 0; status = dorequire(L, "src/luasocket/socket.lua", "socket"); if (status != 0) return 0; status = dorequire(L, "src/luasocket/url.lua", "url"); if (status != 0) return 0; status = dorequire(L, "src/luasocket/ltn12.lua", "ltn12"); if (status != 0) return 0; status = dorequire(L, "src/luasocket/mime.lua", "mime"); if (status != 0) return 0; status = dorequire(L, "src/luasocket/headers.lua", "headers"); if (status != 0) return 0; status = dorequire(L, "src/luasocket/http.lua", "http"); if (status != 0) return 0; status = dorequire(L, "src/luasec/ssl.lua", "ssl"); if (status != 0) return 0; status = dorequire(L, "src/luasec/https.lua", "https"); if (status != 0) return 0; status = dorequire(L, "src/json.lua", "json"); if (status != 0) return 0; status = dorequire(L, "src/jsonf.lua", "jsonf"); if (status != 0) return 0; status = dorequire(L, "src/task.lua", "task"); if (status != 0) return 0; status = dofile(L, "src/file.lua"); if (status != 0) return 0; lua_gc(L, LUA_GCRESTART, 0); s->status = handle_luainit(L); if (s->status != 0) return 0; s->status = load_image(L, argv, 1); if (s->status != 0) return 0; /* call main() */ save_call_graph_depth(L, /*depth*/2, "main"); /* manually seed debug info */ lua_getglobal(L, "main"); s->status = docall(L, 0, 1); if (s->status != 0) return report_in_developer_mode(L, s->status); return 0; } extern void cleanup_curses(void); int main (int argc, char **argv) { int status; struct Smain s; lua_State *L = luaL_newstate(); if (L == NULL) { l_message(argv[0], "cannot create state: not enough memory"); return EXIT_FAILURE; } if (argc == 1) { print_usage(); exit(1); } setlocale(LC_ALL, ""); initscr(); keypad(stdscr, 1); start_color(); assume_default_colors(COLOR_FOREGROUND, COLOR_BACKGROUND); render_trusted_teliva_data(L); echo(); s.argc = argc; s.argv = argv; Argv = argv; status = lua_cpcall(L, &pmain, &s); report(L, status); lua_close(L); cleanup_curses(); return (status || s.status) ? EXIT_FAILURE : EXIT_SUCCESS; } ================================================ FILE: src/lua.h ================================================ /* ** $Id: lua.h,v 1.218.1.7 2012/01/13 20:36:20 roberto Exp $ ** Lua - An Extensible Extension Language ** Lua.org, PUC-Rio, Brazil (http://www.lua.org) ** See Copyright Notice at the end of this file */ #ifndef lua_h #define lua_h #include #include #include "luaconf.h" #define LUA_VERSION "Lua 5.1" #define LUA_RELEASE "Lua 5.1.5" #define LUA_VERSION_NUM 501 #define LUA_COPYRIGHT "Copyright (C) 1994-2012 Lua.org, PUC-Rio" #define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo & W. Celes" extern char *Image_name; /* mark for precompiled code (`Lua') */ #define LUA_SIGNATURE "\033Lua" /* option for multiple returns in `lua_pcall' and `lua_call' */ #define LUA_MULTRET (-1) /* ** pseudo-indices */ #define LUA_REGISTRYINDEX (-10000) #define LUA_ENVIRONINDEX (-10001) #define LUA_GLOBALSINDEX (-10002) #define lua_upvalueindex(i) (LUA_GLOBALSINDEX-(i)) /* thread status; 0 is OK */ #define LUA_YIELD 1 #define LUA_ERRRUN 2 #define LUA_ERRSYNTAX 3 #define LUA_ERRMEM 4 #define LUA_ERRERR 5 typedef struct lua_State lua_State; typedef int (*lua_CFunction) (lua_State *L); /* ** functions that read/write blocks when loading/dumping Lua chunks */ typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz); typedef int (*lua_Writer) (lua_State *L, const void* p, size_t sz, void* ud); /* ** prototype for memory-allocation functions */ typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); /* ** basic types */ #define LUA_TNONE (-1) #define LUA_TNIL 0 #define LUA_TBOOLEAN 1 #define LUA_TLIGHTUSERDATA 2 #define LUA_TNUMBER 3 #define LUA_TSTRING 4 #define LUA_TTABLE 5 #define LUA_TFUNCTION 6 #define LUA_TUSERDATA 7 #define LUA_TTHREAD 8 /* minimum Lua stack available to a C function */ #define LUA_MINSTACK 20 /* ** generic extra include file */ #if defined(LUA_USER_H) #include LUA_USER_H #endif /* type of numbers in Lua */ typedef LUA_NUMBER lua_Number; /* type for integer functions */ typedef LUA_INTEGER lua_Integer; /* ** state manipulation */ LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud); LUA_API void (lua_close) (lua_State *L); LUA_API lua_State *(lua_newthread) (lua_State *L); LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf); /* ** basic stack manipulation */ LUA_API int (lua_gettop) (lua_State *L); LUA_API void (lua_settop) (lua_State *L, int idx); LUA_API void (lua_pushvalue) (lua_State *L, int idx); LUA_API void (lua_remove) (lua_State *L, int idx); LUA_API void (lua_insert) (lua_State *L, int idx); LUA_API void (lua_replace) (lua_State *L, int idx); LUA_API int (lua_checkstack) (lua_State *L, int sz); LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n); /* ** access functions (stack -> C) */ LUA_API int (lua_isnumber) (lua_State *L, int idx); LUA_API int (lua_isstring) (lua_State *L, int idx); LUA_API int (lua_iscfunction) (lua_State *L, int idx); LUA_API int (lua_isuserdata) (lua_State *L, int idx); LUA_API int (lua_type) (lua_State *L, int idx); LUA_API const char *(lua_typename) (lua_State *L, int tp); LUA_API int (lua_equal) (lua_State *L, int idx1, int idx2); LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2); LUA_API int (lua_lessthan) (lua_State *L, int idx1, int idx2); LUA_API lua_Number (lua_tonumber) (lua_State *L, int idx); LUA_API lua_Integer (lua_tointeger) (lua_State *L, int idx); LUA_API int (lua_toboolean) (lua_State *L, int idx); LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); LUA_API size_t (lua_objlen) (lua_State *L, int idx); LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx); LUA_API void *(lua_touserdata) (lua_State *L, int idx); LUA_API lua_State *(lua_tothread) (lua_State *L, int idx); LUA_API const void *(lua_topointer) (lua_State *L, int idx); /* ** push functions (C -> stack) */ LUA_API void (lua_pushnil) (lua_State *L); LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n); LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n); LUA_API void (lua_pushlstring) (lua_State *L, const char *s, size_t l); LUA_API void (lua_pushstring) (lua_State *L, const char *s); LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, va_list argp); LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...); LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n); LUA_API void (lua_pushboolean) (lua_State *L, int b); LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p); LUA_API int (lua_pushthread) (lua_State *L); /* ** get functions (Lua -> stack) */ LUA_API void (lua_gettable) (lua_State *L, int idx); LUA_API void (lua_getfield) (lua_State *L, int idx, const char *k); LUA_API void (lua_rawget) (lua_State *L, int idx); LUA_API void (lua_rawgeti) (lua_State *L, int idx, int n); LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec); LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz); LUA_API int (lua_getmetatable) (lua_State *L, int objindex); LUA_API void (lua_getfenv) (lua_State *L, int idx); /* ** set functions (stack -> Lua) */ LUA_API void (lua_settable) (lua_State *L, int idx); LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k); LUA_API void (lua_rawset) (lua_State *L, int idx); LUA_API void (lua_rawseti) (lua_State *L, int idx, int n); LUA_API int (lua_setmetatable) (lua_State *L, int objindex); LUA_API int (lua_setfenv) (lua_State *L, int idx); /* ** `load' and `call' functions (load and run Lua code) */ LUA_API void (lua_call) (lua_State *L, int nargs, int nresults); LUA_API int (lua_pcall) (lua_State *L, int nargs, int nresults, int errfunc); LUA_API int (lua_cpcall) (lua_State *L, lua_CFunction func, void *ud); LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt, const char *chunkname); LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data); /* ** coroutine functions */ LUA_API int (lua_yield) (lua_State *L, int nresults); LUA_API int (lua_resume) (lua_State *L, int narg); LUA_API int (lua_status) (lua_State *L); /* ** garbage-collection function and options */ #define LUA_GCSTOP 0 #define LUA_GCRESTART 1 #define LUA_GCCOLLECT 2 #define LUA_GCCOUNT 3 #define LUA_GCCOUNTB 4 #define LUA_GCSTEP 5 #define LUA_GCSETPAUSE 6 #define LUA_GCSETSTEPMUL 7 LUA_API int (lua_gc) (lua_State *L, int what, int data); /* ** miscellaneous functions */ LUA_API int (lua_error) (lua_State *L); LUA_API int (lua_next) (lua_State *L, int idx); LUA_API void (lua_concat) (lua_State *L, int n); LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud); LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud); /* ** =============================================================== ** some useful macros ** =============================================================== */ #define lua_pop(L,n) lua_settop(L, -(n)-1) #define lua_newtable(L) lua_createtable(L, 0, 0) #define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) #define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) #define lua_strlen(L,i) lua_objlen(L, (i)) #define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION) #define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE) #define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) #define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL) #define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN) #define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD) #define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE) #define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0) #define lua_pushliteral(L, s) \ lua_pushlstring(L, "" s, (sizeof(s)/sizeof(char))-1) #define lua_setglobal(L,s) lua_setfield(L, LUA_GLOBALSINDEX, (s)) #define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, (s)) #define lua_tostring(L,i) lua_tolstring(L, (i), NULL) /* ** compatibility macros and functions */ #define lua_getregistry(L) lua_pushvalue(L, LUA_REGISTRYINDEX) #define lua_getgccount(L) lua_gc(L, LUA_GCCOUNT, 0) #define lua_Chunkreader lua_Reader #define lua_Chunkwriter lua_Writer /* hack */ LUA_API void lua_setlevel (lua_State *from, lua_State *to); /* ** {====================================================================== ** Debug API ** ======================================================================= */ /* ** Event codes */ #define LUA_HOOKCALL 0 #define LUA_HOOKRET 1 #define LUA_HOOKLINE 2 #define LUA_HOOKCOUNT 3 #define LUA_HOOKTAILRET 4 /* ** Event masks */ #define LUA_MASKCALL (1 << LUA_HOOKCALL) #define LUA_MASKRET (1 << LUA_HOOKRET) #define LUA_MASKLINE (1 << LUA_HOOKLINE) #define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT) typedef struct lua_Debug lua_Debug; /* activation record */ /* Functions to be called by the debuger in specific events */ typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar); LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar); LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n); LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n); LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n); LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n); LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count); LUA_API lua_Hook lua_gethook (lua_State *L); LUA_API int lua_gethookmask (lua_State *L); LUA_API int lua_gethookcount (lua_State *L); struct lua_Debug { int event; const char *name; /* (n) */ const char *namewhat; /* (n) `global', `local', `field', `method' */ const char *what; /* (S) `Lua', `C', `main', `tail' */ const char *source; /* (S) */ int currentline; /* (l) */ int nups; /* (u) number of upvalues */ int linedefined; /* (S) */ int lastlinedefined; /* (S) */ char short_src[LUA_IDSIZE]; /* (S) */ /* private part */ int i_ci; /* active function */ }; /* }====================================================================== */ /****************************************************************************** * Copyright (C) 1994-2012 Lua.org, PUC-Rio. All rights reserved. * * 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. ******************************************************************************/ #endif ================================================ FILE: src/luaconf.h ================================================ /* ** $Id: luaconf.h,v 1.82.1.7 2008/02/11 16:25:08 roberto Exp $ ** Configuration file for Lua ** See Copyright Notice in lua.h */ #ifndef lconfig_h #define lconfig_h #include #include /* ** ================================================================== ** Search for "@@" to find all configurable definitions. ** =================================================================== */ /* @@ LUA_ANSI controls the use of non-ansi features. ** CHANGE it (define it) if you want Lua to avoid the use of any ** non-ansi feature or library. */ #if defined(__STRICT_ANSI__) #define LUA_ANSI #endif #if !defined(LUA_ANSI) && defined(_WIN32) #define LUA_WIN #endif #define LUA_USE_MKSTEMP #define LUA_USE_ISATTY #define LUA_USE_POPEN #define LUA_USE_ULONGJMP /* @@ LUA_PATH and LUA_CPATH are the names of the environment variables that @* Lua check to set its paths. @@ LUA_INIT is the name of the environment variable that Lua @* checks for initialization code. ** CHANGE them if you want different names. */ #define LUA_PATH "LUA_PATH" #define LUA_CPATH "LUA_CPATH" #define LUA_INIT "LUA_INIT" /* @@ LUA_PATH_DEFAULT is the default path that Lua uses to look for @* Lua libraries. @@ LUA_CPATH_DEFAULT is the default path that Lua uses to look for @* C libraries. ** CHANGE them if your machine has a non-conventional directory ** hierarchy or if you want to install your libraries in ** non-conventional directories. */ #if defined(_WIN32) /* ** In Windows, any exclamation mark ('!') in the path is replaced by the ** path of the directory of the executable file of the current process. */ #define LUA_LDIR "!\\lua\\" #define LUA_CDIR "!\\" #define LUA_PATH_DEFAULT \ ".\\?.lua;" LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \ LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua" #define LUA_CPATH_DEFAULT \ ".\\?.dll;" LUA_CDIR"?.dll;" LUA_CDIR"loadall.dll" #else #define LUA_ROOT "/usr/local/" #define LUA_LDIR LUA_ROOT "share/lua/5.1/" #define LUA_CDIR LUA_ROOT "lib/lua/5.1/" #define LUA_PATH_DEFAULT \ "./?.lua;" LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \ LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua" #define LUA_CPATH_DEFAULT \ "./?.so;" LUA_CDIR"?.so;" LUA_CDIR"loadall.so" #endif /* @@ LUA_DIRSEP is the directory separator (for submodules). ** CHANGE it if your machine does not use "/" as the directory separator ** and is not Windows. (On Windows Lua automatically uses "\".) */ #if defined(_WIN32) #define LUA_DIRSEP "\\" #else #define LUA_DIRSEP "/" #endif /* @@ LUA_PATHSEP is the character that separates templates in a path. @@ LUA_PATH_MARK is the string that marks the substitution points in a @* template. @@ LUA_EXECDIR in a Windows path is replaced by the executable's @* directory. @@ LUA_IGMARK is a mark to ignore all before it when bulding the @* luaopen_ function name. ** CHANGE them if for some reason your system cannot use those ** characters. (E.g., if one of those characters is a common character ** in file/directory names.) Probably you do not need to change them. */ #define LUA_PATHSEP ";" #define LUA_PATH_MARK "?" #define LUA_EXECDIR "!" #define LUA_IGMARK "-" /* @@ LUA_INTEGER is the integral type used by lua_pushinteger/lua_tointeger. ** CHANGE that if ptrdiff_t is not adequate on your machine. (On most ** machines, ptrdiff_t gives a good choice between int or long.) */ #define LUA_INTEGER ptrdiff_t /* @@ LUA_API is a mark for all core API functions. @@ LUALIB_API is a mark for all standard library functions. ** CHANGE them if you need to define those functions in some special way. ** For instance, if you want to create one Windows DLL with the core and ** the libraries, you may want to use the following definition (define ** LUA_BUILD_AS_DLL to get it). */ #if defined(LUA_BUILD_AS_DLL) #if defined(LUA_CORE) || defined(LUA_LIB) #define LUA_API __declspec(dllexport) #else #define LUA_API __declspec(dllimport) #endif #else #define LUA_API extern #endif /* more often than not the libs go together with the core */ #define LUALIB_API LUA_API /* @@ LUAI_FUNC is a mark for all extern functions that are not to be @* exported to outside modules. @@ LUAI_DATA is a mark for all extern (const) variables that are not to @* be exported to outside modules. ** CHANGE them if you need to mark them in some special way. Elf/gcc ** (versions 3.2 and later) mark them as "hidden" to optimize access ** when Lua is compiled as a shared library. */ #if defined(luaall_c) #define LUAI_FUNC static #define LUAI_DATA /* empty */ #elif defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \ defined(__ELF__) #define LUAI_FUNC __attribute__((visibility("hidden"))) extern #define LUAI_DATA LUAI_FUNC #else #define LUAI_FUNC extern #define LUAI_DATA extern #endif /* @@ LUA_QL describes how error messages quote program elements. ** CHANGE it if you want a different appearance. */ #define LUA_QL(x) "'" x "'" #define LUA_QS LUA_QL("%s") /* @@ LUA_IDSIZE gives the maximum size for the description of the source @* of a function in debug information. ** CHANGE it if you want a different size. */ #define LUA_IDSIZE 60 /* ** {================================================================== ** Stand-alone configuration ** =================================================================== */ #if defined(lua_c) || defined(luaall_c) /* @@ LUA_PROMPT is the default prompt used by stand-alone Lua. @@ LUA_PROMPT2 is the default continuation prompt used by stand-alone Lua. ** CHANGE them if you want different prompts. (You can also change the ** prompts dynamically, assigning to globals _PROMPT/_PROMPT2.) */ #define LUA_PROMPT "> " #define LUA_PROMPT2 ">> " /* @@ LUA_PROGNAME is the default name for the stand-alone Lua program. ** CHANGE it if your stand-alone interpreter has a different name and ** your system is not able to detect that name automatically. */ #define LUA_PROGNAME "teliva" /* @@ LUA_MAXINPUT is the maximum length for an input line in the @* stand-alone interpreter. ** CHANGE it if you need longer lines. */ #define LUA_MAXINPUT 512 #endif /* }================================================================== */ /* @@ LUAI_GCPAUSE defines the default pause between garbage-collector cycles @* as a percentage. ** CHANGE it if you want the GC to run faster or slower (higher values ** mean larger pauses which mean slower collection.) You can also change ** this value dynamically. */ #define LUAI_GCPAUSE 200 /* 200% (wait memory to double before next GC) */ /* @@ LUAI_GCMUL defines the default speed of garbage collection relative to @* memory allocation as a percentage. ** CHANGE it if you want to change the granularity of the garbage ** collection. (Higher values mean coarser collections. 0 represents ** infinity, where each step performs a full collection.) You can also ** change this value dynamically. */ #define LUAI_GCMUL 200 /* GC runs 'twice the speed' of memory allocation */ /* @@ LUA_COMPAT_GETN controls compatibility with old getn behavior. ** CHANGE it (define it) if you want exact compatibility with the ** behavior of setn/getn in Lua 5.0. */ #undef LUA_COMPAT_GETN /* @@ LUA_COMPAT_VARARG controls compatibility with old vararg feature. ** CHANGE it to undefined as soon as your programs use only '...' to ** access vararg parameters (instead of the old 'arg' table). */ #define LUA_COMPAT_VARARG /* @@ LUA_COMPAT_MOD controls compatibility with old math.mod function. ** CHANGE it to undefined as soon as your programs use 'math.fmod' or ** the new '%' operator instead of 'math.mod'. */ #define LUA_COMPAT_MOD /* @@ LUA_COMPAT_LSTR controls compatibility with old long string nesting @* facility. ** CHANGE it to 2 if you want the old behaviour, or undefine it to turn ** off the advisory error when nesting [[...]]. */ #define LUA_COMPAT_LSTR 1 /* @@ LUA_COMPAT_GFIND controls compatibility with old 'string.gfind' name. ** CHANGE it to undefined as soon as you rename 'string.gfind' to ** 'string.gmatch'. */ #define LUA_COMPAT_GFIND /* @@ LUA_COMPAT_OPENLIB controls compatibility with old 'luaL_openlib' @* behavior. ** CHANGE it to undefined as soon as you replace to 'luaL_register' ** your uses of 'luaL_openlib' */ #define LUA_COMPAT_OPENLIB /* @@ luai_apicheck is the assert macro used by the Lua-C API. ** CHANGE luai_apicheck if you want Lua to perform some checks in the ** parameters it gets from API calls. This may slow down the interpreter ** a bit, but may be quite useful when debugging C code that interfaces ** with Lua. A useful redefinition is to use assert.h. */ #if defined(LUA_USE_APICHECK) #include #define luai_apicheck(L,o) { (void)L; assert(o); } #else #define luai_apicheck(L,o) { (void)L; } #endif /* @@ LUAI_BITSINT defines the number of bits in an int. ** CHANGE here if Lua cannot automatically detect the number of bits of ** your machine. Probably you do not need to change this. */ /* avoid overflows in comparison */ #if INT_MAX-20 < 32760 #define LUAI_BITSINT 16 #elif INT_MAX > 2147483640L /* int has at least 32 bits */ #define LUAI_BITSINT 32 #else #error "you must define LUA_BITSINT with number of bits in an integer" #endif /* @@ LUAI_UINT32 is an unsigned integer with at least 32 bits. @@ LUAI_INT32 is an signed integer with at least 32 bits. @@ LUAI_UMEM is an unsigned integer big enough to count the total @* memory used by Lua. @@ LUAI_MEM is a signed integer big enough to count the total memory @* used by Lua. ** CHANGE here if for some weird reason the default definitions are not ** good enough for your machine. (The definitions in the 'else' ** part always works, but may waste space on machines with 64-bit ** longs.) Probably you do not need to change this. */ #if LUAI_BITSINT >= 32 #define LUAI_UINT32 unsigned int #define LUAI_INT32 int #define LUAI_MAXINT32 INT_MAX #define LUAI_UMEM size_t #define LUAI_MEM ptrdiff_t #else /* 16-bit ints */ #define LUAI_UINT32 unsigned long #define LUAI_INT32 long #define LUAI_MAXINT32 LONG_MAX #define LUAI_UMEM unsigned long #define LUAI_MEM long #endif /* @@ LUAI_MAXCALLS limits the number of nested calls. ** CHANGE it if you need really deep recursive calls. This limit is ** arbitrary; its only purpose is to stop infinite recursion before ** exhausting memory. */ #define LUAI_MAXCALLS 20000 /* @@ LUAI_MAXCSTACK limits the number of Lua stack slots that a C function @* can use. ** CHANGE it if you need lots of (Lua) stack space for your C ** functions. This limit is arbitrary; its only purpose is to stop C ** functions to consume unlimited stack space. (must be smaller than ** -LUA_REGISTRYINDEX) */ #define LUAI_MAXCSTACK 8000 /* ** {================================================================== ** CHANGE (to smaller values) the following definitions if your system ** has a small C stack. (Or you may want to change them to larger ** values if your system has a large C stack and these limits are ** too rigid for you.) Some of these constants control the size of ** stack-allocated arrays used by the compiler or the interpreter, while ** others limit the maximum number of recursive calls that the compiler ** or the interpreter can perform. Values too large may cause a C stack ** overflow for some forms of deep constructs. ** =================================================================== */ /* @@ LUAI_MAXCCALLS is the maximum depth for nested C calls (short) and @* syntactical nested non-terminals in a program. */ #define LUAI_MAXCCALLS 200 /* @@ LUAI_MAXVARS is the maximum number of local variables per function @* (must be smaller than 250). */ #define LUAI_MAXVARS 200 /* @@ LUAI_MAXUPVALUES is the maximum number of upvalues per function @* (must be smaller than 250). */ #define LUAI_MAXUPVALUES 60 /* @@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system. */ #define LUAL_BUFFERSIZE BUFSIZ /* }================================================================== */ /* ** {================================================================== @@ LUA_NUMBER is the type of numbers in Lua. ** CHANGE the following definitions only if you want to build Lua ** with a number type different from double. You may also need to ** change lua_number2int & lua_number2integer. ** =================================================================== */ #define LUA_NUMBER_DOUBLE #define LUA_NUMBER double /* @@ LUAI_UACNUMBER is the result of an 'usual argument conversion' @* over a number. */ #define LUAI_UACNUMBER double /* @@ LUA_NUMBER_SCAN is the format for reading numbers. @@ LUA_NUMBER_FMT is the format for writing numbers. @@ lua_number2str converts a number to a string. @@ LUAI_MAXNUMBER2STR is maximum size of previous conversion. @@ lua_str2number converts a string to a number. */ #define LUA_NUMBER_SCAN "%lf" #define LUA_NUMBER_FMT "%.14g" #define lua_number2str(s,n) sprintf((s), LUA_NUMBER_FMT, (n)) #define LUAI_MAXNUMBER2STR 32 /* 16 digits, sign, point, and \0 */ #define lua_str2number(s,p) strtod((s), (p)) /* @@ The luai_num* macros define the primitive operations over numbers. */ #if defined(LUA_CORE) #include #define luai_numadd(a,b) ((a)+(b)) #define luai_numsub(a,b) ((a)-(b)) #define luai_nummul(a,b) ((a)*(b)) #define luai_numdiv(a,b) ((a)/(b)) #define luai_nummod(a,b) ((a) - floor((a)/(b))*(b)) #define luai_numpow(a,b) (pow(a,b)) #define luai_numunm(a) (-(a)) #define luai_numeq(a,b) ((a)==(b)) #define luai_numlt(a,b) ((a)<(b)) #define luai_numle(a,b) ((a)<=(b)) #define luai_numisnan(a) (!luai_numeq((a), (a))) #endif /* @@ lua_number2int is a macro to convert lua_Number to int. @@ lua_number2integer is a macro to convert lua_Number to lua_Integer. ** CHANGE them if you know a faster way to convert a lua_Number to ** int (with any rounding method and without throwing errors) in your ** system. In Pentium machines, a naive typecast from double to int ** in C is extremely slow, so any alternative is worth trying. */ /* On a Pentium, resort to a trick */ #if defined(LUA_NUMBER_DOUBLE) && !defined(LUA_ANSI) && !defined(__SSE2__) && \ (defined(__i386) || defined (_M_IX86) || defined(__i386__)) /* On a Microsoft compiler, use assembler */ #if defined(_MSC_VER) #define lua_number2int(i,d) __asm fld d __asm fistp i #define lua_number2integer(i,n) lua_number2int(i, n) /* the next trick should work on any Pentium, but sometimes clashes with a DirectX idiosyncrasy */ #else union luai_Cast { double l_d; long l_l; }; #define lua_number2int(i,d) \ { volatile union luai_Cast u; u.l_d = (d) + 6755399441055744.0; (i) = u.l_l; } #define lua_number2integer(i,n) lua_number2int(i, n) #endif /* this option always works, but may be slow */ #else #define lua_number2int(i,d) ((i)=(int)(d)) #define lua_number2integer(i,d) ((i)=(lua_Integer)(d)) #endif /* }================================================================== */ /* @@ LUAI_USER_ALIGNMENT_T is a type that requires maximum alignment. ** CHANGE it if your system requires alignments larger than double. (For ** instance, if your system supports long doubles and they must be ** aligned in 16-byte boundaries, then you should add long double in the ** union.) Probably you do not need to change this. */ #define LUAI_USER_ALIGNMENT_T union { double u; void *s; long l; } /* @@ LUAI_THROW/LUAI_TRY define how Lua does exception handling. ** CHANGE them if you prefer to use longjmp/setjmp even with C++ ** or if want/don't to use _longjmp/_setjmp instead of regular ** longjmp/setjmp. By default, Lua handles errors with exceptions when ** compiling as C++ code, with _longjmp/_setjmp when asked to use them, ** and with longjmp/setjmp otherwise. */ #if defined(__cplusplus) /* C++ exceptions */ #define LUAI_THROW(L,c) throw(c) #define LUAI_TRY(L,c,a) try { a } catch(...) \ { if ((c)->status == 0) (c)->status = -1; } #define luai_jmpbuf int /* dummy variable */ #elif defined(LUA_USE_ULONGJMP) /* in Unix, try _longjmp/_setjmp (more efficient) */ #define LUAI_THROW(L,c) _longjmp((c)->b, 1) #define LUAI_TRY(L,c,a) if (_setjmp((c)->b) == 0) { a } #define luai_jmpbuf jmp_buf #else /* default handling with long jumps */ #define LUAI_THROW(L,c) longjmp((c)->b, 1) #define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a } #define luai_jmpbuf jmp_buf #endif /* @@ LUA_MAXCAPTURES is the maximum number of captures that a pattern @* can do during pattern-matching. ** CHANGE it if you need more captures. This limit is arbitrary. */ #define LUA_MAXCAPTURES 32 /* @@ lua_tmpnam is the function that the OS library uses to create a @* temporary name. @@ LUA_TMPNAMBUFSIZE is the maximum size of a name created by lua_tmpnam. ** CHANGE them if you have an alternative to tmpnam (which is considered ** insecure) or if you want the original tmpnam anyway. By default, Lua ** uses tmpnam except when POSIX is available, where it uses mkstemp. */ #if defined(loslib_c) || defined(luaall_c) #if defined(LUA_USE_MKSTEMP) #include /* we have newer libraries even though the dialect is C99 */ extern int mkstemp(char *); #define LUA_TMPNAMBUFSIZE 32 #define lua_tmpnam(b,e) { \ strcpy(b, "/tmp/lua_XXXXXX"); \ e = mkstemp(b); \ if (e != -1) close(e); \ e = (e == -1); } #else #define LUA_TMPNAMBUFSIZE L_tmpnam #define lua_tmpnam(b,e) { e = (tmpnam(b) == NULL); } #endif #endif /* @@ LUA_INTFRMLEN is the length modifier for integer conversions @* in 'string.format'. @@ LUA_INTFRM_T is the integer type correspoding to the previous length @* modifier. ** CHANGE them if your system supports long long or does not support long. */ #if defined(LUA_USELONGLONG) #define LUA_INTFRMLEN "ll" #define LUA_INTFRM_T long long #else #define LUA_INTFRMLEN "l" #define LUA_INTFRM_T long #endif /* =================================================================== */ /* ** Local configuration. You can use this space to add your redefinitions ** without modifying the main part of the file. */ #endif ================================================ FILE: src/lualib.h ================================================ /* ** $Id: lualib.h,v 1.36.1.1 2007/12/27 13:02:25 roberto Exp $ ** Lua standard libraries ** See Copyright Notice in lua.h */ #ifndef lualib_h #define lualib_h #include "lua.h" /* Key to file-handle type */ #define LUA_FILEHANDLE "FILE*" #define LUA_COLIBNAME "coroutine" LUALIB_API int (luaopen_base) (lua_State *L); #define LUA_TABLIBNAME "table" LUALIB_API int (luaopen_table) (lua_State *L); #define LUA_IOLIBNAME "io" LUALIB_API int (luaopen_io) (lua_State *L); #define LUA_OSLIBNAME "os" LUALIB_API int (luaopen_os) (lua_State *L); #define LUA_STRLIBNAME "string" LUALIB_API int (luaopen_string) (lua_State *L); #define LUA_MATHLIBNAME "math" LUALIB_API int (luaopen_math) (lua_State *L); #define LUA_CURSESLIBNAME "curses" LUALIB_API int (luaopen_curses) (lua_State *L); #define LUA_SOCKETCORELIBNAME "socket" LUALIB_API int (luaopen_socket_core) (lua_State *L); #define LUA_MIMECORELIBNAME "mime" LUALIB_API int (luaopen_mime_core) (lua_State *L); #define LUA_SSLLIBNAME "ssl" LUALIB_API int (luaopen_ssl_core) (lua_State *L); #define LUA_SSLCONTEXTLIBNAME "context" LUALIB_API int (luaopen_ssl_context) (lua_State *L); #define LUA_SSLX509LIBNAME "x509" LUALIB_API int (luaopen_ssl_x509) (lua_State *L); #define LUA_SSLCONFIGLIBNAME "config" LUALIB_API int (luaopen_ssl_config) (lua_State *L); #define LUA_DBLIBNAME "debug" LUALIB_API int (luaopen_debug) (lua_State *L); /* open all previous libraries */ LUALIB_API void (luaL_openlibs) (lua_State *L); #ifndef lua_assert #define lua_assert(x) ((void)0) #endif #endif ================================================ FILE: src/luasec/Makefile ================================================ CMOD=ssl.a LMOD=ssl.lua OBJS= \ options.o \ x509.o \ context.o \ ssl.o \ config.o \ ec.o WARN=-Wall BSD_CFLAGS=-O2 -fPIC $(WARN) $(DEFS) BSD_LDFLAGS=-O -fPIC -shared linux_CFLAGS=-g -O2 -std=c99 $(WARN) -Wpedantic $(DEFS) macosx_ENV=env MACOSX_DEPLOYMENT_TARGET='$(MACVER)' macosx_CFLAGS=-g -O2 -std=c99 -fno-common $(WARN) -Wpedantic $(DEFS) -I/usr/local/opt/openssl@3/include macosx_LDFLAGS=-bundle -undefined dynamic_lookup -L/usr/local/opt/openssl@3/lib INSTALL = install CC = gcc CCLD ?= $(MYENV) $(CC) CFLAGS = $(MYCFLAGS) LDFLAGS += $(MYLDFLAGS) AR= ar rc RANLIB= ranlib .PHONY: all clean install none linux bsd macosx all: install: $(CMOD) $(LMOD) $(INSTALL) -d $(DESTDIR)$(LUAPATH)/ssl $(DESTDIR)$(LUACPATH) $(INSTALL) $(CMOD) $(DESTDIR)$(LUACPATH) $(INSTALL) -m644 $(LMOD) $(DESTDIR)$(LUAPATH) $(INSTALL) -m644 https.lua $(DESTDIR)$(LUAPATH)/ssl linux: @$(MAKE) $(CMOD) MYCFLAGS="$(linux_CFLAGS)" MYLDFLAGS="$(linux_LDFLAGS)" EXTRA="$(EXTRA)" bsd: @$(MAKE) $(CMOD) MYCFLAGS="$(BSD_CFLAGS)" MYLDFLAGS="$(BSD_LDFLAGS)" EXTRA="$(EXTRA)" macosx: @$(MAKE) $(CMOD) MYCFLAGS="$(macosx_CFLAGS)" MYLDFLAGS="$(macosx_LDFLAGS)" MYENV="$(macosx_ENV)" EXTRA="$(EXTRA)" $(CMOD): $(OBJS) $(AR) $(CMOD) $(OBJS) $(RANLIB) $(CMOD) clean: rm -f $(OBJS) $(CMOD) options.o: options.h options.c ec.o: ec.c ec.h x509.o: x509.c x509.h compat.h context.o: context.c context.h ec.h compat.h options.h ssl.o: ssl.c ssl.h context.h x509.h compat.h config.o: config.c ec.h options.h compat.h ================================================ FILE: src/luasec/compat.h ================================================ /*-------------------------------------------------------------------------- * LuaSec 1.0.2 * * Copyright (C) 2006-2021 Bruno Silvestre * *--------------------------------------------------------------------------*/ #ifndef LSEC_COMPAT_H #define LSEC_COMPAT_H #include //------------------------------------------------------------------------------ #if defined(_WIN32) #define LSEC_API __declspec(dllexport) #else #define LSEC_API extern #endif //------------------------------------------------------------------------------ #define setfuncs(L, R) luaL_register(L, NULL, R) #define lua_rawlen(L, i) lua_objlen(L, i) #ifndef luaL_newlib #define luaL_newlib(L, R) do { lua_newtable(L); luaL_register(L, NULL, R); } while(0) #endif //------------------------------------------------------------------------------ #if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x1010000fL)) #define LSEC_ENABLE_DANE #endif //------------------------------------------------------------------------------ #if !((defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x2070000fL)) || (OPENSSL_VERSION_NUMBER < 0x1010000fL)) #define LSEC_API_OPENSSL_1_1_0 #endif //------------------------------------------------------------------------------ #if !defined(LIBRESSL_VERSION_NUMBER) && ((OPENSSL_VERSION_NUMBER & 0xFFFFF000L) == 0x10101000L) #define LSEC_OPENSSL_1_1_1 #endif //------------------------------------------------------------------------------ #endif ================================================ FILE: src/luasec/config.c ================================================ /*-------------------------------------------------------------------------- * LuaSec 1.0.2 * * Copyright (C) 2006-2021 Bruno Silvestre. * *--------------------------------------------------------------------------*/ #include "compat.h" #include "options.h" #include "ec.h" /** * Registre the module. */ LSEC_API int luaopen_ssl_config(lua_State *L) { lsec_ssl_option_t *opt; lua_newtable(L); lua_pushvalue(L, -1); lua_setglobal(L, "config"); // Options lua_pushstring(L, "options"); lua_newtable(L); for (opt = lsec_get_ssl_options(); opt->name; opt++) { lua_pushstring(L, opt->name); lua_pushboolean(L, 1); lua_rawset(L, -3); } lua_rawset(L, -3); // Protocols lua_pushstring(L, "protocols"); lua_newtable(L); lua_pushstring(L, "tlsv1"); lua_pushboolean(L, 1); lua_rawset(L, -3); lua_pushstring(L, "tlsv1_1"); lua_pushboolean(L, 1); lua_rawset(L, -3); lua_pushstring(L, "tlsv1_2"); lua_pushboolean(L, 1); lua_rawset(L, -3); #ifdef TLS1_3_VERSION lua_pushstring(L, "tlsv1_3"); lua_pushboolean(L, 1); lua_rawset(L, -3); #endif lua_rawset(L, -3); // Algorithms lua_pushstring(L, "algorithms"); lua_newtable(L); #ifndef OPENSSL_NO_EC lua_pushstring(L, "ec"); lua_pushboolean(L, 1); lua_rawset(L, -3); #endif lua_rawset(L, -3); // Curves lua_pushstring(L, "curves"); lsec_get_curves(L); lua_rawset(L, -3); // Capabilities lua_pushstring(L, "capabilities"); lua_newtable(L); // ALPN lua_pushstring(L, "alpn"); lua_pushboolean(L, 1); lua_rawset(L, -3); #ifdef LSEC_ENABLE_DANE // DANE lua_pushstring(L, "dane"); lua_pushboolean(L, 1); lua_rawset(L, -3); #endif #ifndef OPENSSL_NO_EC lua_pushstring(L, "curves_list"); lua_pushboolean(L, 1); lua_rawset(L, -3); lua_pushstring(L, "ecdh_auto"); lua_pushboolean(L, 1); lua_rawset(L, -3); #endif lua_rawset(L, -3); return 1; } ================================================ FILE: src/luasec/context.c ================================================ /*-------------------------------------------------------------------------- * LuaSec 1.0.2 * * Copyright (C) 2014-2021 Kim Alvefur, Paul Aurich, Tobias Markmann, * Matthew Wild. * Copyright (C) 2006-2021 Bruno Silvestre. * *--------------------------------------------------------------------------*/ #include #if defined(WIN32) #include #endif #include #include #include #include #include #include "../lua.h" #include "../lauxlib.h" #include "compat.h" #include "context.h" #include "options.h" #ifndef OPENSSL_NO_EC #include #include "ec.h" #endif /*--------------------------- Auxiliary Functions ----------------------------*/ /** * Return the context. */ static p_context checkctx(lua_State *L, int idx) { return (p_context)luaL_checkudata(L, idx, "SSL:Context"); } static p_context testctx(lua_State *L, int idx) { return (p_context)luasocket_testudata(L, idx, "SSL:Context"); } /** * Prepare the SSL options flag. */ static int set_option_flag(const char *opt, unsigned long *flag) { lsec_ssl_option_t *p; for (p = lsec_get_ssl_options(); p->name; p++) { if (!strcmp(opt, p->name)) { *flag |= p->code; return 1; } } return 0; } #ifndef LSEC_API_OPENSSL_1_1_0 /** * Find the protocol. */ static const SSL_METHOD* str2method(const char *method, int *vmin, int *vmax) { (void)vmin; (void)vmax; if (!strcmp(method, "any")) return SSLv23_method(); if (!strcmp(method, "sslv23")) return SSLv23_method(); // deprecated if (!strcmp(method, "tlsv1")) return TLSv1_method(); if (!strcmp(method, "tlsv1_1")) return TLSv1_1_method(); if (!strcmp(method, "tlsv1_2")) return TLSv1_2_method(); return NULL; } #else /** * Find the protocol. */ static const SSL_METHOD* str2method(const char *method, int *vmin, int *vmax) { if (!strcmp(method, "any") || !strcmp(method, "sslv23")) { // 'sslv23' is deprecated *vmin = 0; *vmax = 0; return TLS_method(); } else if (!strcmp(method, "tlsv1")) { *vmin = TLS1_VERSION; *vmax = TLS1_VERSION; return TLS_method(); } else if (!strcmp(method, "tlsv1_1")) { *vmin = TLS1_1_VERSION; *vmax = TLS1_1_VERSION; return TLS_method(); } else if (!strcmp(method, "tlsv1_2")) { *vmin = TLS1_2_VERSION; *vmax = TLS1_2_VERSION; return TLS_method(); } #if defined(TLS1_3_VERSION) else if (!strcmp(method, "tlsv1_3")) { *vmin = TLS1_3_VERSION; *vmax = TLS1_3_VERSION; return TLS_method(); } #endif return NULL; } #endif /** * Prepare the SSL handshake verify flag. */ static int set_verify_flag(const char *str, int *flag) { if (!strcmp(str, "none")) { *flag |= SSL_VERIFY_NONE; return 1; } if (!strcmp(str, "peer")) { *flag |= SSL_VERIFY_PEER; return 1; } if (!strcmp(str, "client_once")) { *flag |= SSL_VERIFY_CLIENT_ONCE; return 1; } if (!strcmp(str, "fail_if_no_peer_cert")) { *flag |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; return 1; } return 0; } /** * Password callback for reading the private key. */ static int passwd_cb(char *buf, int size, int flag, void *udata) { lua_State *L = (lua_State*)udata; switch (lua_type(L, 3)) { case LUA_TFUNCTION: lua_pushvalue(L, 3); lua_call(L, 0, 1); if (lua_type(L, -1) != LUA_TSTRING) { lua_pop(L, 1); /* Remove the result from the stack */ return 0; } /* fall through */ case LUA_TSTRING: strncpy(buf, lua_tostring(L, -1), size); lua_pop(L, 1); /* Remove the result from the stack */ buf[size-1] = '\0'; return (int)strlen(buf); } return 0; } /** * Add an error related to a depth certificate of the chain. */ static void add_cert_error(lua_State *L, SSL *ssl, int err, int depth) { luaL_getmetatable(L, "SSL:Verify:Registry"); lua_pushlightuserdata(L, (void*)ssl); lua_gettable(L, -2); if (lua_isnil(L, -1)) { lua_pop(L, 1); /* Create an error table for this connection */ lua_newtable(L); lua_pushlightuserdata(L, (void*)ssl); lua_pushvalue(L, -2); /* keep the table on stack */ lua_settable(L, -4); } lua_rawgeti(L, -1, depth+1); /* If the table doesn't exist, create it */ if (lua_isnil(L, -1)) { lua_pop(L, 1); /* remove 'nil' from stack */ lua_newtable(L); lua_pushvalue(L, -1); /* keep the table on stack */ lua_rawseti(L, -3, depth+1); } lua_pushstring(L, X509_verify_cert_error_string(err)); lua_rawseti(L, -2, lua_rawlen(L, -2) + 1); /* Clear the stack */ lua_pop(L, 3); } /** * Call Lua user function to get the DH key. */ static DH *dhparam_cb(SSL *ssl, int is_export, int keylength) { BIO *bio; lua_State *L; SSL_CTX *ctx = SSL_get_SSL_CTX(ssl); p_context pctx = (p_context)SSL_CTX_get_app_data(ctx); L = pctx->L; /* Get the callback */ luaL_getmetatable(L, "SSL:DH:Registry"); lua_pushlightuserdata(L, (void*)ctx); lua_gettable(L, -2); /* Invoke the callback */ lua_pushboolean(L, is_export); lua_pushnumber(L, keylength); lua_call(L, 2, 1); /* Load parameters from returned value */ if (lua_type(L, -1) != LUA_TSTRING) { lua_pop(L, 2); /* Remove values from stack */ return NULL; } bio = BIO_new_mem_buf((void*)lua_tostring(L, -1), lua_rawlen(L, -1)); if (bio) { pctx->dh_param = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); BIO_free(bio); } lua_pop(L, 2); /* Remove values from stack */ return pctx->dh_param; } /** * Set the "ignore purpose" before to start verifing the certificate chain. */ static int cert_verify_cb(X509_STORE_CTX *x509_ctx, void *ptr) { int verify; lua_State *L; SSL_CTX *ctx = (SSL_CTX*)ptr; p_context pctx = (p_context)SSL_CTX_get_app_data(ctx); L = pctx->L; /* Get verify flags */ luaL_getmetatable(L, "SSL:Verify:Registry"); lua_pushlightuserdata(L, (void*)ctx); lua_gettable(L, -2); verify = (int)lua_tonumber(L, -1); lua_pop(L, 2); /* Remove values from stack */ if (verify & LSEC_VERIFY_IGNORE_PURPOSE) { /* Set parameters to ignore the server purpose */ X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(x509_ctx); if (param) { X509_VERIFY_PARAM_set_purpose(param, X509_PURPOSE_SSL_SERVER); X509_VERIFY_PARAM_set_trust(param, X509_TRUST_SSL_SERVER); } } /* Call OpenSSL standard verification function */ return X509_verify_cert(x509_ctx); } /** * This callback implements the "continue on error" flag and log the errors. */ static int verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) { int err; int verify; SSL *ssl; SSL_CTX *ctx; p_context pctx; lua_State *L; /* Short-circuit optimization */ if (preverify_ok) return 1; ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); ctx = SSL_get_SSL_CTX(ssl); pctx = (p_context)SSL_CTX_get_app_data(ctx); L = pctx->L; /* Get verify flags */ luaL_getmetatable(L, "SSL:Verify:Registry"); lua_pushlightuserdata(L, (void*)ctx); lua_gettable(L, -2); verify = (int)lua_tonumber(L, -1); lua_pop(L, 2); /* Remove values from stack */ err = X509_STORE_CTX_get_error(x509_ctx); if (err != X509_V_OK) add_cert_error(L, ssl, err, X509_STORE_CTX_get_error_depth(x509_ctx)); return (verify & LSEC_VERIFY_CONTINUE ? 1 : preverify_ok); } /*------------------------------ Lua Functions -------------------------------*/ /** * Create a SSL context. */ static int create(lua_State *L) { p_context ctx; const char *str_method; const SSL_METHOD *method; int vmin, vmax; str_method = luaL_checkstring(L, 1); method = str2method(str_method, &vmin, &vmax); if (!method) { lua_pushnil(L); lua_pushfstring(L, "invalid protocol (%s)", str_method); return 2; } ctx = (p_context) lua_newuserdata(L, sizeof(t_context)); if (!ctx) { lua_pushnil(L); lua_pushstring(L, "error creating context"); return 2; } memset(ctx, 0, sizeof(t_context)); ctx->context = SSL_CTX_new(method); if (!ctx->context) { lua_pushnil(L); lua_pushfstring(L, "error creating context (%s)", ERR_reason_error_string(ERR_get_error())); return 2; } #ifdef LSEC_API_OPENSSL_1_1_0 SSL_CTX_set_min_proto_version(ctx->context, vmin); SSL_CTX_set_max_proto_version(ctx->context, vmax); #endif ctx->mode = LSEC_MODE_INVALID; ctx->L = L; luaL_getmetatable(L, "SSL:Context"); lua_setmetatable(L, -2); /* No session support */ SSL_CTX_set_session_cache_mode(ctx->context, SSL_SESS_CACHE_OFF); /* Link LuaSec context with the OpenSSL context */ SSL_CTX_set_app_data(ctx->context, ctx); return 1; } /** * Load the trusting certificates. */ static int load_locations(lua_State *L) { SSL_CTX *ctx = lsec_checkcontext(L, 1); const char *cafile = luaL_optstring(L, 2, NULL); const char *capath = luaL_optstring(L, 3, NULL); if (SSL_CTX_load_verify_locations(ctx, cafile, capath) != 1) { lua_pushboolean(L, 0); lua_pushfstring(L, "error loading CA locations (%s)", ERR_reason_error_string(ERR_get_error())); return 2; } lua_pushboolean(L, 1); return 1; } /** * Load the certificate file. */ static int load_cert(lua_State *L) { SSL_CTX *ctx = lsec_checkcontext(L, 1); const char *filename = luaL_checkstring(L, 2); if (SSL_CTX_use_certificate_chain_file(ctx, filename) != 1) { lua_pushboolean(L, 0); lua_pushfstring(L, "error loading certificate (%s)", ERR_reason_error_string(ERR_get_error())); return 2; } lua_pushboolean(L, 1); return 1; } /** * Load the key file -- only in PEM format. */ static int load_key(lua_State *L) { int ret = 1; SSL_CTX *ctx = lsec_checkcontext(L, 1); const char *filename = luaL_checkstring(L, 2); switch (lua_type(L, 3)) { case LUA_TSTRING: case LUA_TFUNCTION: SSL_CTX_set_default_passwd_cb(ctx, passwd_cb); SSL_CTX_set_default_passwd_cb_userdata(ctx, L); /* fall through */ case LUA_TNIL: if (SSL_CTX_use_PrivateKey_file(ctx, filename, SSL_FILETYPE_PEM) == 1) lua_pushboolean(L, 1); else { ret = 2; lua_pushboolean(L, 0); lua_pushfstring(L, "error loading private key (%s)", ERR_reason_error_string(ERR_get_error())); } SSL_CTX_set_default_passwd_cb(ctx, NULL); SSL_CTX_set_default_passwd_cb_userdata(ctx, NULL); break; default: lua_pushstring(L, "invalid callback value"); lua_error(L); } return ret; } /** * Check that the certificate public key matches the private key */ static int check_key(lua_State *L) { SSL_CTX *ctx = lsec_checkcontext(L, 1); lua_pushboolean(L, SSL_CTX_check_private_key(ctx)); return 1; } /** * Set the cipher list. */ static int set_cipher(lua_State *L) { SSL_CTX *ctx = lsec_checkcontext(L, 1); const char *list = luaL_checkstring(L, 2); if (SSL_CTX_set_cipher_list(ctx, list) != 1) { lua_pushboolean(L, 0); lua_pushfstring(L, "error setting cipher list (%s)", ERR_reason_error_string(ERR_get_error())); return 2; } lua_pushboolean(L, 1); return 1; } /** * Set the cipher suites. */ static int set_ciphersuites(lua_State *L) { #if defined(TLS1_3_VERSION) SSL_CTX *ctx = lsec_checkcontext(L, 1); const char *list = luaL_checkstring(L, 2); if (SSL_CTX_set_ciphersuites(ctx, list) != 1) { lua_pushboolean(L, 0); lua_pushfstring(L, "error setting cipher list (%s)", ERR_reason_error_string(ERR_get_error())); return 2; } #endif lua_pushboolean(L, 1); return 1; } /** * Set the depth for certificate checking. */ static int set_depth(lua_State *L) { SSL_CTX *ctx = lsec_checkcontext(L, 1); SSL_CTX_set_verify_depth(ctx, (int)luaL_checkinteger(L, 2)); lua_pushboolean(L, 1); return 1; } /** * Set the handshake verify options. */ static int set_verify(lua_State *L) { int i; const char *str; int flag = 0; SSL_CTX *ctx = lsec_checkcontext(L, 1); int max = lua_gettop(L); for (i = 2; i <= max; i++) { str = luaL_checkstring(L, i); if (!set_verify_flag(str, &flag)) { lua_pushboolean(L, 0); lua_pushfstring(L, "invalid verify option (%s)", str); return 2; } } if (flag) SSL_CTX_set_verify(ctx, flag, NULL); lua_pushboolean(L, 1); return 1; } /** * Set the protocol options. */ static int set_options(lua_State *L) { int i; const char *str; unsigned long flag = 0L; SSL_CTX *ctx = lsec_checkcontext(L, 1); int max = lua_gettop(L); /* any option? */ if (max > 1) { for (i = 2; i <= max; i++) { str = luaL_checkstring(L, i); if (!set_option_flag(str, &flag)) { lua_pushboolean(L, 0); lua_pushfstring(L, "invalid option (%s)", str); return 2; } } SSL_CTX_set_options(ctx, flag); } lua_pushboolean(L, 1); return 1; } /** * Set the context mode. */ static int set_mode(lua_State *L) { p_context ctx = checkctx(L, 1); const char *str = luaL_checkstring(L, 2); if (!strcmp("server", str)) { ctx->mode = LSEC_MODE_SERVER; lua_pushboolean(L, 1); return 1; } if (!strcmp("client", str)) { ctx->mode = LSEC_MODE_CLIENT; lua_pushboolean(L, 1); return 1; } lua_pushboolean(L, 0); lua_pushfstring(L, "invalid mode (%s)", str); return 1; } /** * Configure DH parameters. */ static int set_dhparam(lua_State *L) { SSL_CTX *ctx = lsec_checkcontext(L, 1); SSL_CTX_set_tmp_dh_callback(ctx, dhparam_cb); /* Save callback */ luaL_getmetatable(L, "SSL:DH:Registry"); lua_pushlightuserdata(L, (void*)ctx); lua_pushvalue(L, 2); lua_settable(L, -3); return 0; } #if !defined(OPENSSL_NO_EC) /** * Set elliptic curve. */ static int set_curve(lua_State *L) { long ret; EC_KEY *key = NULL; SSL_CTX *ctx = lsec_checkcontext(L, 1); const char *str = luaL_checkstring(L, 2); SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); key = lsec_find_ec_key(L, str); if (!key) { lua_pushboolean(L, 0); lua_pushfstring(L, "elliptic curve '%s' not supported", str); return 2; } ret = SSL_CTX_set_tmp_ecdh(ctx, key); /* SSL_CTX_set_tmp_ecdh takes its own reference */ EC_KEY_free(key); if (!ret) { lua_pushboolean(L, 0); lua_pushfstring(L, "error setting elliptic curve (%s)", ERR_reason_error_string(ERR_get_error())); return 2; } lua_pushboolean(L, 1); return 1; } /** * Set elliptic curves list. */ static int set_curves_list(lua_State *L) { SSL_CTX *ctx = lsec_checkcontext(L, 1); const char *str = luaL_checkstring(L, 2); SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); if (SSL_CTX_set1_curves_list(ctx, str) != 1) { lua_pushboolean(L, 0); lua_pushfstring(L, "unknown elliptic curve in \"%s\"", str); return 2; } #if defined(LIBRESSL_VERSION_NUMBER) || !defined(LSEC_API_OPENSSL_1_1_0) (void)SSL_CTX_set_ecdh_auto(ctx, 1); #endif lua_pushboolean(L, 1); return 1; } #endif /** * Set the protocols a client should send for ALPN. */ static int set_alpn(lua_State *L) { long ret; size_t len; p_context ctx = checkctx(L, 1); const char *str = luaL_checklstring(L, 2, &len); ret = SSL_CTX_set_alpn_protos(ctx->context, (const unsigned char*)str, len); if (ret) { lua_pushboolean(L, 0); lua_pushfstring(L, "error setting ALPN (%s)", ERR_reason_error_string(ERR_get_error())); return 2; } lua_pushboolean(L, 1); return 1; } /** * This standard callback calls the server's callback in Lua sapce. * The server has to return a list in wire-format strings. * This function uses a helper function to match server and client lists. */ static int alpn_cb(SSL *s, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { int ret; size_t server_len; const char *server; p_context ctx = (p_context)arg; lua_State *L = ctx->L; luaL_getmetatable(L, "SSL:ALPN:Registry"); lua_pushlightuserdata(L, (void*)ctx->context); lua_gettable(L, -2); lua_pushlstring(L, (const char*)in, inlen); lua_call(L, 1, 1); if (!lua_isstring(L, -1)) { lua_pop(L, 2); return SSL_TLSEXT_ERR_NOACK; } // Protocol list from server in wire-format string server = luaL_checklstring(L, -1, &server_len); ret = SSL_select_next_proto((unsigned char**)out, outlen, (const unsigned char*)server, server_len, in, inlen); if (ret != OPENSSL_NPN_NEGOTIATED) { lua_pop(L, 2); return SSL_TLSEXT_ERR_NOACK; } // Copy the result because lua_pop() can collect the pointer ctx->alpn = malloc(*outlen); memcpy(ctx->alpn, (void*)*out, *outlen); *out = (const unsigned char*)ctx->alpn; lua_pop(L, 2); return SSL_TLSEXT_ERR_OK; } /** * Set a callback a server can use to select the next protocol with ALPN. */ static int set_alpn_cb(lua_State *L) { p_context ctx = checkctx(L, 1); luaL_getmetatable(L, "SSL:ALPN:Registry"); lua_pushlightuserdata(L, (void*)ctx->context); lua_pushvalue(L, 2); lua_settable(L, -3); SSL_CTX_set_alpn_select_cb(ctx->context, alpn_cb, ctx); lua_pushboolean(L, 1); return 1; } #if defined(LSEC_ENABLE_DANE) /* * DANE */ static int set_dane(lua_State *L) { int ret; SSL_CTX *ctx = lsec_checkcontext(L, 1); ret = SSL_CTX_dane_enable(ctx); lua_pushboolean(L, (ret > 0)); return 1; } #endif /** * Package functions */ static luaL_Reg funcs[] = { {"create", create}, {"locations", load_locations}, {"loadcert", load_cert}, {"loadkey", load_key}, {"checkkey", check_key}, {"setalpn", set_alpn}, {"setalpncb", set_alpn_cb}, {"setcipher", set_cipher}, {"setciphersuites", set_ciphersuites}, {"setdepth", set_depth}, {"setdhparam", set_dhparam}, {"setverify", set_verify}, {"setoptions", set_options}, {"setmode", set_mode}, #if !defined(OPENSSL_NO_EC) {"setcurve", set_curve}, {"setcurveslist", set_curves_list}, #endif #if defined(LSEC_ENABLE_DANE) {"setdane", set_dane}, #endif {NULL, NULL} }; /*-------------------------------- Metamethods -------------------------------*/ /** * Collect SSL context -- GC metamethod. */ static int meth_destroy(lua_State *L) { p_context ctx = checkctx(L, 1); if (ctx->context) { /* Clear registries */ luaL_getmetatable(L, "SSL:DH:Registry"); lua_pushlightuserdata(L, (void*)ctx->context); lua_pushnil(L); lua_settable(L, -3); luaL_getmetatable(L, "SSL:Verify:Registry"); lua_pushlightuserdata(L, (void*)ctx->context); lua_pushnil(L); lua_settable(L, -3); luaL_getmetatable(L, "SSL:ALPN:Registry"); lua_pushlightuserdata(L, (void*)ctx->context); lua_pushnil(L); lua_settable(L, -3); SSL_CTX_free(ctx->context); ctx->context = NULL; } return 0; } /** * Object information -- tostring metamethod. */ static int meth_tostring(lua_State *L) { p_context ctx = checkctx(L, 1); lua_pushfstring(L, "SSL context: %p", ctx); return 1; } /** * Set extra flags for handshake verification. */ static int meth_set_verify_ext(lua_State *L) { int i; const char *str; int crl_flag = 0; int lsec_flag = 0; SSL_CTX *ctx = lsec_checkcontext(L, 1); int max = lua_gettop(L); for (i = 2; i <= max; i++) { str = luaL_checkstring(L, i); if (!strcmp(str, "lsec_continue")) { lsec_flag |= LSEC_VERIFY_CONTINUE; } else if (!strcmp(str, "lsec_ignore_purpose")) { lsec_flag |= LSEC_VERIFY_IGNORE_PURPOSE; } else if (!strcmp(str, "crl_check")) { crl_flag |= X509_V_FLAG_CRL_CHECK; } else if (!strcmp(str, "crl_check_chain")) { crl_flag |= X509_V_FLAG_CRL_CHECK_ALL; } else { lua_pushboolean(L, 0); lua_pushfstring(L, "invalid verify option (%s)", str); return 2; } } /* Set callback? */ if (lsec_flag) { SSL_CTX_set_verify(ctx, SSL_CTX_get_verify_mode(ctx), verify_cb); SSL_CTX_set_cert_verify_callback(ctx, cert_verify_cb, (void*)ctx); /* Save flag */ luaL_getmetatable(L, "SSL:Verify:Registry"); lua_pushlightuserdata(L, (void*)ctx); lua_pushnumber(L, lsec_flag); lua_settable(L, -3); } else { SSL_CTX_set_verify(ctx, SSL_CTX_get_verify_mode(ctx), NULL); SSL_CTX_set_cert_verify_callback(ctx, NULL, NULL); /* Remove flag */ luaL_getmetatable(L, "SSL:Verify:Registry"); lua_pushlightuserdata(L, (void*)ctx); lua_pushnil(L); lua_settable(L, -3); } /* X509 flag */ X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), crl_flag); /* Ok */ lua_pushboolean(L, 1); return 1; } /** * Context metamethods. */ static luaL_Reg meta[] = { {"__close", meth_destroy}, {"__gc", meth_destroy}, {"__tostring", meth_tostring}, {NULL, NULL} }; /** * Index metamethods. */ static luaL_Reg meta_index[] = { {"setverifyext", meth_set_verify_ext}, {NULL, NULL} }; /*----------------------------- Public Functions ---------------------------*/ /** * Retrieve the SSL context from the Lua stack. */ SSL_CTX* lsec_checkcontext(lua_State *L, int idx) { p_context ctx = checkctx(L, idx); return ctx->context; } SSL_CTX* lsec_testcontext(lua_State *L, int idx) { p_context ctx = testctx(L, idx); return (ctx) ? ctx->context : NULL; } /** * Retrieve the mode from the context in the Lua stack. */ int lsec_getmode(lua_State *L, int idx) { p_context ctx = checkctx(L, idx); return ctx->mode; } /*------------------------------ Initialization ------------------------------*/ /** * Registre the module. */ LSEC_API int luaopen_ssl_context(lua_State *L) { luaL_newmetatable(L, "SSL:DH:Registry"); /* Keep all DH callbacks */ luaL_newmetatable(L, "SSL:ALPN:Registry"); /* Keep all ALPN callbacks */ luaL_newmetatable(L, "SSL:Verify:Registry"); /* Keep all verify flags */ luaL_newmetatable(L, "SSL:Context"); setfuncs(L, meta); /* Create __index metamethods for context */ luaL_newlib(L, meta_index); lua_setfield(L, -2, "__index"); lsec_load_curves(L); /* Return the module */ luaL_newlib(L, funcs); lua_pushvalue(L, -1); lua_setglobal(L, "context"); return 1; } ================================================ FILE: src/luasec/context.h ================================================ #ifndef LSEC_CONTEXT_H #define LSEC_CONTEXT_H /*-------------------------------------------------------------------------- * LuaSec 1.0.2 * * Copyright (C) 2006-2021 Bruno Silvestre * *--------------------------------------------------------------------------*/ #include "../lua.h" #include #include "compat.h" #define LSEC_MODE_INVALID 0 #define LSEC_MODE_SERVER 1 #define LSEC_MODE_CLIENT 2 #define LSEC_VERIFY_CONTINUE 1 #define LSEC_VERIFY_IGNORE_PURPOSE 2 typedef struct t_context_ { SSL_CTX *context; lua_State *L; DH *dh_param; void *alpn; int mode; } t_context; typedef t_context* p_context; /* Retrieve the SSL context from the Lua stack */ SSL_CTX *lsec_checkcontext(lua_State *L, int idx); SSL_CTX *lsec_testcontext(lua_State *L, int idx); /* Retrieve the mode from the context in the Lua stack */ int lsec_getmode(lua_State *L, int idx); /* Registre the module. */ LSEC_API int luaopen_ssl_context(lua_State *L); void *luasocket_testudata ( lua_State *L, int arg, const char *tname); #endif ================================================ FILE: src/luasec/ec.c ================================================ #include #include "ec.h" #ifndef OPENSSL_NO_EC EC_KEY *lsec_find_ec_key(lua_State *L, const char *str) { int nid; lua_pushstring(L, "SSL:EC:CURVES"); lua_rawget(L, LUA_REGISTRYINDEX); lua_pushstring(L, str); lua_rawget(L, -2); if (!lua_isnumber(L, -1)) return NULL; nid = (int)lua_tonumber(L, -1); return EC_KEY_new_by_curve_name(nid); } void lsec_load_curves(lua_State *L) { size_t i; size_t size; const char *name; EC_builtin_curve *curves = NULL; lua_pushstring(L, "SSL:EC:CURVES"); lua_newtable(L); size = EC_get_builtin_curves(NULL, 0); if (size > 0) { curves = (EC_builtin_curve*)malloc(sizeof(EC_builtin_curve) * size); EC_get_builtin_curves(curves, size); for (i = 0; i < size; i++) { name = OBJ_nid2sn(curves[i].nid); if (name != NULL) { lua_pushstring(L, name); lua_pushnumber(L, curves[i].nid); lua_rawset(L, -3); } switch (curves[i].nid) { case NID_X9_62_prime256v1: lua_pushstring(L, "P-256"); lua_pushnumber(L, curves[i].nid); lua_rawset(L, -3); break; case NID_secp384r1: lua_pushstring(L, "P-384"); lua_pushnumber(L, curves[i].nid); lua_rawset(L, -3); break; case NID_secp521r1: lua_pushstring(L, "P-521"); lua_pushnumber(L, curves[i].nid); lua_rawset(L, -3); break; } } free(curves); } /* These are special so are manually added here */ #ifdef NID_X25519 lua_pushstring(L, "X25519"); lua_pushnumber(L, NID_X25519); lua_rawset(L, -3); #endif #ifdef NID_X448 lua_pushstring(L, "X448"); lua_pushnumber(L, NID_X448); lua_rawset(L, -3); #endif lua_rawset(L, LUA_REGISTRYINDEX); } void lsec_get_curves(lua_State *L) { lua_newtable(L); lua_pushstring(L, "SSL:EC:CURVES"); lua_rawget(L, LUA_REGISTRYINDEX); lua_pushnil(L); while (lua_next(L, -2) != 0) { lua_pop(L, 1); lua_pushvalue(L, -1); lua_pushboolean(L, 1); lua_rawset(L, -5); } lua_pop(L, 1); } #else void lsec_load_curves(lua_State *L) { // do nothing } void lsec_get_curves(lua_State *L) { lua_newtable(L); } #endif ================================================ FILE: src/luasec/ec.h ================================================ /*-------------------------------------------------------------------------- * LuaSec 1.0.2 * * Copyright (C) 2006-2021 Bruno Silvestre * *--------------------------------------------------------------------------*/ #ifndef LSEC_EC_H #define LSEC_EC_H #include "../lua.h" #ifndef OPENSSL_NO_EC #include EC_KEY *lsec_find_ec_key(lua_State *L, const char *str); #endif void lsec_get_curves(lua_State *L); void lsec_load_curves(lua_State *L); #endif ================================================ FILE: src/luasec/https.lua ================================================ ---------------------------------------------------------------------------- -- LuaSec 1.0.2 -- Copyright (C) 2009-2021 PUC-Rio -- -- Author: Pablo Musa -- Author: Tomas Guisasola --------------------------------------------------------------------------- local try = socket.try -- -- Module -- local _M = { _VERSION = "1.0.2", _COPYRIGHT = "LuaSec 1.0.2 - Copyright (C) 2009-2021 PUC-Rio", PORT = 443, TIMEOUT = 60 } -- TLS configuration local cfg = { protocol = "any", options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"}, verify = "none", } -------------------------------------------------------------------- -- Auxiliar Functions -------------------------------------------------------------------- -- Insert default HTTPS port. local function default_https_port(u) return url.build(url.parse(u, {port = _M.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, _M.TIMEOUT) 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)) self.sock:sni(host) self.sock:settimeout(_M.TIMEOUT) 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 -- local 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 -------------------------------------------------------------------------------- -- Export module -- _M.request = request _M.tcp = tcp return _M ================================================ FILE: src/luasec/options.c ================================================ /*-------------------------------------------------------------------------- * LuaSec 1.0.2 * * Copyright (C) 2006-2021 Bruno Silvestre * *--------------------------------------------------------------------------*/ #include #include "options.h" /* If you need to generate these options again, see options.lua */ /* OpenSSL version: OpenSSL 3.0.0-beta2 */ static lsec_ssl_option_t ssl_options[] = { #if defined(SSL_OP_ALL) {"all", SSL_OP_ALL}, #endif #if defined(SSL_OP_ALLOW_CLIENT_RENEGOTIATION) {"allow_client_renegotiation", SSL_OP_ALLOW_CLIENT_RENEGOTIATION}, #endif #if defined(SSL_OP_ALLOW_NO_DHE_KEX) {"allow_no_dhe_kex", SSL_OP_ALLOW_NO_DHE_KEX}, #endif #if defined(SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION) {"allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION}, #endif #if defined(SSL_OP_CIPHER_SERVER_PREFERENCE) {"cipher_server_preference", SSL_OP_CIPHER_SERVER_PREFERENCE}, #endif #if defined(SSL_OP_CISCO_ANYCONNECT) {"cisco_anyconnect", SSL_OP_CISCO_ANYCONNECT}, #endif #if defined(SSL_OP_CLEANSE_PLAINTEXT) {"cleanse_plaintext", SSL_OP_CLEANSE_PLAINTEXT}, #endif #if defined(SSL_OP_COOKIE_EXCHANGE) {"cookie_exchange", SSL_OP_COOKIE_EXCHANGE}, #endif #if defined(SSL_OP_CRYPTOPRO_TLSEXT_BUG) {"cryptopro_tlsext_bug", SSL_OP_CRYPTOPRO_TLSEXT_BUG}, #endif #if defined(SSL_OP_DISABLE_TLSEXT_CA_NAMES) {"disable_tlsext_ca_names", SSL_OP_DISABLE_TLSEXT_CA_NAMES}, #endif #if defined(SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) {"dont_insert_empty_fragments", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS}, #endif #if defined(SSL_OP_ENABLE_KTLS) {"enable_ktls", SSL_OP_ENABLE_KTLS}, #endif #if defined(SSL_OP_ENABLE_MIDDLEBOX_COMPAT) {"enable_middlebox_compat", SSL_OP_ENABLE_MIDDLEBOX_COMPAT}, #endif #if defined(SSL_OP_EPHEMERAL_RSA) {"ephemeral_rsa", SSL_OP_EPHEMERAL_RSA}, #endif #if defined(SSL_OP_IGNORE_UNEXPECTED_EOF) {"ignore_unexpected_eof", SSL_OP_IGNORE_UNEXPECTED_EOF}, #endif #if defined(SSL_OP_LEGACY_SERVER_CONNECT) {"legacy_server_connect", SSL_OP_LEGACY_SERVER_CONNECT}, #endif #if defined(SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER) {"microsoft_big_sslv3_buffer", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER}, #endif #if defined(SSL_OP_MICROSOFT_SESS_ID_BUG) {"microsoft_sess_id_bug", SSL_OP_MICROSOFT_SESS_ID_BUG}, #endif #if defined(SSL_OP_MSIE_SSLV2_RSA_PADDING) {"msie_sslv2_rsa_padding", SSL_OP_MSIE_SSLV2_RSA_PADDING}, #endif #if defined(SSL_OP_NETSCAPE_CA_DN_BUG) {"netscape_ca_dn_bug", SSL_OP_NETSCAPE_CA_DN_BUG}, #endif #if defined(SSL_OP_NETSCAPE_CHALLENGE_BUG) {"netscape_challenge_bug", SSL_OP_NETSCAPE_CHALLENGE_BUG}, #endif #if defined(SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG) {"netscape_demo_cipher_change_bug", SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG}, #endif #if defined(SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG) {"netscape_reuse_cipher_change_bug", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG}, #endif #if defined(SSL_OP_NO_ANTI_REPLAY) {"no_anti_replay", SSL_OP_NO_ANTI_REPLAY}, #endif #if defined(SSL_OP_NO_COMPRESSION) {"no_compression", SSL_OP_NO_COMPRESSION}, #endif #if defined(SSL_OP_NO_DTLS_MASK) {"no_dtls_mask", SSL_OP_NO_DTLS_MASK}, #endif #if defined(SSL_OP_NO_DTLSv1) {"no_dtlsv1", SSL_OP_NO_DTLSv1}, #endif #if defined(SSL_OP_NO_DTLSv1_2) {"no_dtlsv1_2", SSL_OP_NO_DTLSv1_2}, #endif #if defined(SSL_OP_NO_ENCRYPT_THEN_MAC) {"no_encrypt_then_mac", SSL_OP_NO_ENCRYPT_THEN_MAC}, #endif #if defined(SSL_OP_NO_EXTENDED_MASTER_SECRET) {"no_extended_master_secret", SSL_OP_NO_EXTENDED_MASTER_SECRET}, #endif #if defined(SSL_OP_NO_QUERY_MTU) {"no_query_mtu", SSL_OP_NO_QUERY_MTU}, #endif #if defined(SSL_OP_NO_RENEGOTIATION) {"no_renegotiation", SSL_OP_NO_RENEGOTIATION}, #endif #if defined(SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION) {"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION}, #endif #if defined(SSL_OP_NO_SSL_MASK) {"no_ssl_mask", SSL_OP_NO_SSL_MASK}, #endif #if defined(SSL_OP_NO_SSLv2) {"no_sslv2", SSL_OP_NO_SSLv2}, #endif #if defined(SSL_OP_NO_SSLv3) {"no_sslv3", SSL_OP_NO_SSLv3}, #endif #if defined(SSL_OP_NO_TICKET) {"no_ticket", SSL_OP_NO_TICKET}, #endif #if defined(SSL_OP_NO_TLSv1) {"no_tlsv1", SSL_OP_NO_TLSv1}, #endif #if defined(SSL_OP_NO_TLSv1_1) {"no_tlsv1_1", SSL_OP_NO_TLSv1_1}, #endif #if defined(SSL_OP_NO_TLSv1_2) {"no_tlsv1_2", SSL_OP_NO_TLSv1_2}, #endif #if defined(SSL_OP_NO_TLSv1_3) {"no_tlsv1_3", SSL_OP_NO_TLSv1_3}, #endif #if defined(SSL_OP_PKCS1_CHECK_1) {"pkcs1_check_1", SSL_OP_PKCS1_CHECK_1}, #endif #if defined(SSL_OP_PKCS1_CHECK_2) {"pkcs1_check_2", SSL_OP_PKCS1_CHECK_2}, #endif #if defined(SSL_OP_PRIORITIZE_CHACHA) {"prioritize_chacha", SSL_OP_PRIORITIZE_CHACHA}, #endif #if defined(SSL_OP_SAFARI_ECDHE_ECDSA_BUG) {"safari_ecdhe_ecdsa_bug", SSL_OP_SAFARI_ECDHE_ECDSA_BUG}, #endif #if defined(SSL_OP_SINGLE_DH_USE) {"single_dh_use", SSL_OP_SINGLE_DH_USE}, #endif #if defined(SSL_OP_SINGLE_ECDH_USE) {"single_ecdh_use", SSL_OP_SINGLE_ECDH_USE}, #endif #if defined(SSL_OP_SSLEAY_080_CLIENT_DH_BUG) {"ssleay_080_client_dh_bug", SSL_OP_SSLEAY_080_CLIENT_DH_BUG}, #endif #if defined(SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG) {"sslref2_reuse_cert_type_bug", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG}, #endif #if defined(SSL_OP_TLSEXT_PADDING) {"tlsext_padding", SSL_OP_TLSEXT_PADDING}, #endif #if defined(SSL_OP_TLS_BLOCK_PADDING_BUG) {"tls_block_padding_bug", SSL_OP_TLS_BLOCK_PADDING_BUG}, #endif #if defined(SSL_OP_TLS_D5_BUG) {"tls_d5_bug", SSL_OP_TLS_D5_BUG}, #endif #if defined(SSL_OP_TLS_ROLLBACK_BUG) {"tls_rollback_bug", SSL_OP_TLS_ROLLBACK_BUG}, #endif {NULL, 0L} }; LSEC_API lsec_ssl_option_t* lsec_get_ssl_options() { return ssl_options; } ================================================ FILE: src/luasec/options.h ================================================ #ifndef LSEC_OPTIONS_H #define LSEC_OPTIONS_H /*-------------------------------------------------------------------------- * LuaSec 1.0.2 * * Copyright (C) 2006-2021 Bruno Silvestre * *--------------------------------------------------------------------------*/ #include "compat.h" struct lsec_ssl_option_s { const char *name; unsigned long code; }; typedef struct lsec_ssl_option_s lsec_ssl_option_t; LSEC_API lsec_ssl_option_t* lsec_get_ssl_options(); #endif ================================================ FILE: src/luasec/options.lua ================================================ local function usage() print("Usage:") print("* Generate options of your system:") print(" lua options.lua -g /path/to/ssl.h [version] > options.c") print("* Examples:") print(" lua options.lua -g /usr/include/openssl/ssl.h > options.c\n") print(" lua options.lua -g /usr/include/openssl/ssl.h \"OpenSSL 1.1.1f\" > options.c\n") print("* List options of your system:") print(" lua options.lua -l /path/to/ssl.h\n") end -- local function printf(str, ...) print(string.format(str, ...)) end local function generate(options, version) print([[ /*-------------------------------------------------------------------------- * LuaSec 1.1.1 * * Copyright (C) 2006-2021 Bruno Silvestre * *--------------------------------------------------------------------------*/ #include #include "options.h" /* If you need to generate these options again, see options.lua */ ]]) printf([[ /* OpenSSL version: %s */ ]], version) print([[static lsec_ssl_option_t ssl_options[] = {]]) for k, option in ipairs(options) do local name = string.lower(string.sub(option, 8)) print(string.format([[#if defined(%s)]], option)) print(string.format([[ {"%s", %s},]], name, option)) print([[#endif]]) end print([[ {NULL, 0L}]]) print([[ }; LSEC_API lsec_ssl_option_t* lsec_get_ssl_options() { return ssl_options; } ]]) end local function loadoptions(file) local options = {} local f = assert(io.open(file, "r")) for line in f:lines() do local op = string.match(line, "define%s+(SSL_OP_BIT%()") if not op then op = string.match(line, "define%s+(SSL_OP_%S+)") if op then table.insert(options, op) end end end table.sort(options, function(a,b) return a #include #if defined(WIN32) #include #endif #include #include #include #include #include #include "../lua.h" #include "../lauxlib.h" #include "../luasocket/io.h" #include "../luasocket/buffer.h" #include "../luasocket/timeout.h" #include "../luasocket/socket.h" #include "x509.h" #include "context.h" #include "ssl.h" #ifndef LSEC_API_OPENSSL_1_1_0 #define SSL_is_server(s) (s->server) #define SSL_up_ref(ssl) CRYPTO_add(&(ssl)->references, 1, CRYPTO_LOCK_SSL) #define X509_up_ref(c) CRYPTO_add(&c->references, 1, CRYPTO_LOCK_X509) #endif /** * Underline socket error. */ static int lsec_socket_error() { #if defined(WIN32) return WSAGetLastError(); #else #if defined(LSEC_OPENSSL_1_1_1) // Bug in OpenSSL 1.1.1 if (errno == 0) return LSEC_IO_SSL; #endif return errno; #endif } /** * Map error code into string. */ static const char *ssl_ioerror(void *ctx, int err) { if (err == LSEC_IO_SSL) { p_ssl ssl = (p_ssl) ctx; switch(ssl->error) { case SSL_ERROR_NONE: return "No error"; case SSL_ERROR_ZERO_RETURN: return "closed"; case SSL_ERROR_WANT_READ: return "wantread"; case SSL_ERROR_WANT_WRITE: return "wantwrite"; case SSL_ERROR_WANT_CONNECT: return "'connect' not completed"; case SSL_ERROR_WANT_ACCEPT: return "'accept' not completed"; case SSL_ERROR_WANT_X509_LOOKUP: return "Waiting for callback"; case SSL_ERROR_SYSCALL: return "System error"; case SSL_ERROR_SSL: return ERR_reason_error_string(ERR_get_error()); default: return "Unknown SSL error"; } } return socket_strerror(err); } /** * Close the connection before the GC collect the object. */ static int meth_destroy(lua_State *L) { p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); if (ssl->state == LSEC_STATE_CONNECTED) { socket_setblocking(&ssl->sock); SSL_shutdown(ssl->ssl); } if (ssl->sock != SOCKET_INVALID) { socket_destroy(&ssl->sock); } ssl->state = LSEC_STATE_CLOSED; if (ssl->ssl) { /* Clear the registries */ luaL_getmetatable(L, "SSL:Verify:Registry"); lua_pushlightuserdata(L, (void*)ssl->ssl); lua_pushnil(L); lua_settable(L, -3); luaL_getmetatable(L, "SSL:SNI:Registry"); lua_pushlightuserdata(L, (void*)ssl->ssl); lua_pushnil(L); lua_settable(L, -3); /* Destroy the object */ SSL_free(ssl->ssl); ssl->ssl = NULL; } return 0; } /** * Perform the TLS/SSL handshake */ static int handshake(p_ssl ssl) { int err; p_timeout tm = timeout_markstart(&ssl->tm); if (ssl->state == LSEC_STATE_CLOSED) return IO_CLOSED; for ( ; ; ) { ERR_clear_error(); err = SSL_do_handshake(ssl->ssl); ssl->error = SSL_get_error(ssl->ssl, err); switch (ssl->error) { case SSL_ERROR_NONE: ssl->state = LSEC_STATE_CONNECTED; return IO_DONE; case SSL_ERROR_WANT_READ: err = socket_waitfd(&ssl->sock, WAITFD_R, tm); if (err == IO_TIMEOUT) return LSEC_IO_SSL; if (err != IO_DONE) return err; break; case SSL_ERROR_WANT_WRITE: err = socket_waitfd(&ssl->sock, WAITFD_W, tm); if (err == IO_TIMEOUT) return LSEC_IO_SSL; if (err != IO_DONE) return err; break; case SSL_ERROR_SYSCALL: if (ERR_peek_error()) { ssl->error = SSL_ERROR_SSL; return LSEC_IO_SSL; } if (err == 0) return IO_CLOSED; return lsec_socket_error(); default: return LSEC_IO_SSL; } } return IO_UNKNOWN; } /** * Send data */ static int ssl_send(void *ctx, const char *data, size_t count, size_t *sent, p_timeout tm) { int err; p_ssl ssl = (p_ssl)ctx; if (ssl->state != LSEC_STATE_CONNECTED) return IO_CLOSED; *sent = 0; for ( ; ; ) { ERR_clear_error(); err = SSL_write(ssl->ssl, data, (int)count); ssl->error = SSL_get_error(ssl->ssl, err); switch (ssl->error) { case SSL_ERROR_NONE: *sent = err; return IO_DONE; case SSL_ERROR_WANT_READ: err = socket_waitfd(&ssl->sock, WAITFD_R, tm); if (err == IO_TIMEOUT) return LSEC_IO_SSL; if (err != IO_DONE) return err; break; case SSL_ERROR_WANT_WRITE: err = socket_waitfd(&ssl->sock, WAITFD_W, tm); if (err == IO_TIMEOUT) return LSEC_IO_SSL; if (err != IO_DONE) return err; break; case SSL_ERROR_SYSCALL: if (ERR_peek_error()) { ssl->error = SSL_ERROR_SSL; return LSEC_IO_SSL; } if (err == 0) return IO_CLOSED; return lsec_socket_error(); default: return LSEC_IO_SSL; } } return IO_UNKNOWN; } /** * Receive data */ static int ssl_recv(void *ctx, char *data, size_t count, size_t *got, p_timeout tm) { int err; p_ssl ssl = (p_ssl)ctx; *got = 0; if (ssl->state != LSEC_STATE_CONNECTED) return IO_CLOSED; for ( ; ; ) { ERR_clear_error(); err = SSL_read(ssl->ssl, data, (int)count); ssl->error = SSL_get_error(ssl->ssl, err); switch (ssl->error) { case SSL_ERROR_NONE: *got = err; return IO_DONE; case SSL_ERROR_ZERO_RETURN: return IO_CLOSED; case SSL_ERROR_WANT_READ: err = socket_waitfd(&ssl->sock, WAITFD_R, tm); if (err == IO_TIMEOUT) return LSEC_IO_SSL; if (err != IO_DONE) return err; break; case SSL_ERROR_WANT_WRITE: err = socket_waitfd(&ssl->sock, WAITFD_W, tm); if (err == IO_TIMEOUT) return LSEC_IO_SSL; if (err != IO_DONE) return err; break; case SSL_ERROR_SYSCALL: if (ERR_peek_error()) { ssl->error = SSL_ERROR_SSL; return LSEC_IO_SSL; } if (err == 0) return IO_CLOSED; return lsec_socket_error(); default: return LSEC_IO_SSL; } } return IO_UNKNOWN; } static SSL_CTX* luaossl_testcontext(lua_State *L, int arg) { SSL_CTX **ctx = luaL_testudata(L, arg, "SSL_CTX*"); if (ctx) return *ctx; return NULL; } static SSL* luaossl_testssl(lua_State *L, int arg) { SSL **ssl = luaL_testudata(L, arg, "SSL*"); if (ssl) return *ssl; return NULL; } /** * Create a new TLS/SSL object and mark it as new. */ static int meth_create(lua_State *L) { p_ssl ssl; int mode; SSL_CTX *ctx; lua_settop(L, 1); ssl = (p_ssl)lua_newuserdata(L, sizeof(t_ssl)); if (!ssl) { lua_pushnil(L); lua_pushstring(L, "error creating SSL object"); return 2; } if ((ctx = lsec_testcontext(L, 1))) { mode = lsec_getmode(L, 1); if (mode == LSEC_MODE_INVALID) { lua_pushnil(L); lua_pushstring(L, "invalid mode"); return 2; } ssl->ssl = SSL_new(ctx); if (!ssl->ssl) { lua_pushnil(L); lua_pushfstring(L, "error creating SSL object (%s)", ERR_reason_error_string(ERR_get_error())); return 2; } } else if ((ctx = luaossl_testcontext(L, 1))) { ssl->ssl = SSL_new(ctx); if (!ssl->ssl) { lua_pushnil(L); lua_pushfstring(L, "error creating SSL object (%s)", ERR_reason_error_string(ERR_get_error())); return 2; } mode = SSL_is_server(ssl->ssl) ? LSEC_MODE_SERVER : LSEC_MODE_CLIENT; } else if ((ssl->ssl = luaossl_testssl(L, 1))) { SSL_up_ref(ssl->ssl); mode = SSL_is_server(ssl->ssl) ? LSEC_MODE_SERVER : LSEC_MODE_CLIENT; } else { return luaL_argerror(L, 1, "invalid context"); } ssl->state = LSEC_STATE_NEW; SSL_set_fd(ssl->ssl, (int)SOCKET_INVALID); SSL_set_mode(ssl->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); SSL_set_mode(ssl->ssl, SSL_MODE_RELEASE_BUFFERS); if (mode == LSEC_MODE_SERVER) SSL_set_accept_state(ssl->ssl); else SSL_set_connect_state(ssl->ssl); io_init(&ssl->io, (p_send)ssl_send, (p_recv)ssl_recv, (p_error) ssl_ioerror, ssl); timeout_init(&ssl->tm, -1, -1); buffer_init(&ssl->buf, &ssl->io, &ssl->tm); luaL_getmetatable(L, "SSL:Connection"); lua_setmetatable(L, -2); return 1; } /** * Buffer send function */ static int meth_send(lua_State *L) { p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); return buffer_meth_send(L, &ssl->buf); } /** * Buffer receive function */ static int meth_receive(lua_State *L) { p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); return buffer_meth_receive(L, &ssl->buf); } /** * Get the buffer's statistics. */ static int meth_getstats(lua_State *L) { p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); return buffer_meth_getstats(L, &ssl->buf); } /** * Set the buffer's statistics. */ static int meth_setstats(lua_State *L) { p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); return buffer_meth_setstats(L, &ssl->buf); } /** * Select support methods */ static int meth_getfd(lua_State *L) { p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); lua_pushnumber(L, ssl->sock); return 1; } /** * Set the TLS/SSL file descriptor. * Call it *before* the handshake. */ static int meth_setfd(lua_State *L) { p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); if (ssl->state != LSEC_STATE_NEW) luaL_argerror(L, 1, "invalid SSL object state"); ssl->sock = (t_socket)luaL_checkinteger(L, 2); socket_setnonblocking(&ssl->sock); SSL_set_fd(ssl->ssl, (int)ssl->sock); return 0; } /** * Lua handshake function. */ static int meth_handshake(lua_State *L) { int err; p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); p_context ctx = (p_context)SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl->ssl)); ctx->L = L; err = handshake(ssl); if (ctx->dh_param) { DH_free(ctx->dh_param); ctx->dh_param = NULL; } if (ctx->alpn) { free(ctx->alpn); ctx->alpn = NULL; } if (err == IO_DONE) { lua_pushboolean(L, 1); return 1; } lua_pushboolean(L, 0); lua_pushstring(L, ssl_ioerror((void*)ssl, err)); return 2; } /** * Close the connection. */ static int meth_close(lua_State *L) { meth_destroy(L); return 0; } /** * Set timeout. */ static int meth_settimeout(lua_State *L) { p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); return timeout_meth_settimeout(L, &ssl->tm); } /** * Check if there is data in the buffer. */ static int meth_dirty(lua_State *L) { int res = 0; p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); if (ssl->state != LSEC_STATE_CLOSED) res = !buffer_isempty(&ssl->buf) || SSL_pending(ssl->ssl); lua_pushboolean(L, res); return 1; } /** * Return the state information about the SSL object. */ static int meth_want(lua_State *L) { p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); int code = (ssl->state == LSEC_STATE_CLOSED) ? SSL_NOTHING : SSL_want(ssl->ssl); switch(code) { case SSL_NOTHING: lua_pushstring(L, "nothing"); break; case SSL_READING: lua_pushstring(L, "read"); break; case SSL_WRITING: lua_pushstring(L, "write"); break; case SSL_X509_LOOKUP: lua_pushstring(L, "x509lookup"); break; } return 1; } /** * Return the compression method used. */ static int meth_compression(lua_State *L) { #ifdef OPENSSL_NO_COMP const void *comp; #else const COMP_METHOD *comp; #endif p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); if (ssl->state != LSEC_STATE_CONNECTED) { lua_pushnil(L); lua_pushstring(L, "closed"); return 2; } comp = SSL_get_current_compression(ssl->ssl); if (comp) lua_pushstring(L, SSL_COMP_get_name(comp)); else lua_pushnil(L); return 1; } /** * Return the nth certificate of the peer's chain. */ static int meth_getpeercertificate(lua_State *L) { int n; X509 *cert; STACK_OF(X509) *certs; p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); if (ssl->state != LSEC_STATE_CONNECTED) { lua_pushnil(L); lua_pushstring(L, "closed"); return 2; } /* Default to the first cert */ n = (int)luaL_optinteger(L, 2, 1); /* This function is 1-based, but OpenSSL is 0-based */ --n; if (n < 0) { lua_pushnil(L); lua_pushliteral(L, "invalid certificate index"); return 2; } if (n == 0) { cert = SSL_get_peer_certificate(ssl->ssl); if (cert) lsec_pushx509(L, cert); else lua_pushnil(L); return 1; } /* In a server-context, the stack doesn't contain the peer cert, * so adjust accordingly. */ if (SSL_is_server(ssl->ssl)) --n; certs = SSL_get_peer_cert_chain(ssl->ssl); if (n >= sk_X509_num(certs)) { lua_pushnil(L); return 1; } cert = sk_X509_value(certs, n); /* Increment the reference counting of the object. */ /* See SSL_get_peer_certificate() source code. */ X509_up_ref(cert); lsec_pushx509(L, cert); return 1; } /** * Return the chain of certificate of the peer. */ static int meth_getpeerchain(lua_State *L) { int i; int idx = 1; int n_certs; X509 *cert; STACK_OF(X509) *certs; p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); if (ssl->state != LSEC_STATE_CONNECTED) { lua_pushnil(L); lua_pushstring(L, "closed"); return 2; } lua_newtable(L); if (SSL_is_server(ssl->ssl)) { lsec_pushx509(L, SSL_get_peer_certificate(ssl->ssl)); lua_rawseti(L, -2, idx++); } certs = SSL_get_peer_cert_chain(ssl->ssl); n_certs = sk_X509_num(certs); for (i = 0; i < n_certs; i++) { cert = sk_X509_value(certs, i); /* Increment the reference counting of the object. */ /* See SSL_get_peer_certificate() source code. */ X509_up_ref(cert); lsec_pushx509(L, cert); lua_rawseti(L, -2, idx++); } return 1; } /** * Copy the table src to the table dst. */ static void copy_error_table(lua_State *L, int src, int dst) { lua_pushnil(L); while (lua_next(L, src) != 0) { if (lua_istable(L, -1)) { /* Replace the table with its copy */ lua_newtable(L); copy_error_table(L, dst+2, dst+3); lua_remove(L, dst+2); } lua_pushvalue(L, -2); lua_pushvalue(L, -2); lua_rawset(L, dst); /* Remove the value and leave the key */ lua_pop(L, 1); } } /** * Return the verification state of the peer chain. */ static int meth_getpeerverification(lua_State *L) { long err; p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); if (ssl->state != LSEC_STATE_CONNECTED) { lua_pushboolean(L, 0); lua_pushstring(L, "closed"); return 2; } err = SSL_get_verify_result(ssl->ssl); if (err == X509_V_OK) { lua_pushboolean(L, 1); return 1; } luaL_getmetatable(L, "SSL:Verify:Registry"); lua_pushlightuserdata(L, (void*)ssl->ssl); lua_gettable(L, -2); if (lua_isnil(L, -1)) lua_pushstring(L, X509_verify_cert_error_string(err)); else { /* Copy the table of errors to avoid modifications */ lua_newtable(L); copy_error_table(L, lua_gettop(L)-1, lua_gettop(L)); } lua_pushboolean(L, 0); lua_pushvalue(L, -2); return 2; } /** * Get the latest "Finished" message sent out. */ static int meth_getfinished(lua_State *L) { size_t len = 0; char *buffer = NULL; p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); if (ssl->state != LSEC_STATE_CONNECTED) { lua_pushnil(L); lua_pushstring(L, "closed"); return 2; } if ((len = SSL_get_finished(ssl->ssl, NULL, 0)) == 0) return 0; buffer = (char*)malloc(len); if (!buffer) { lua_pushnil(L); lua_pushstring(L, "out of memory"); return 2; } SSL_get_finished(ssl->ssl, buffer, len); lua_pushlstring(L, buffer, len); free(buffer); return 1; } /** * Gets the latest "Finished" message received. */ static int meth_getpeerfinished(lua_State *L) { size_t len = 0; char *buffer = NULL; p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); if (ssl->state != LSEC_STATE_CONNECTED) { lua_pushnil(L); lua_pushstring(L, "closed"); return 0; } if ((len = SSL_get_peer_finished(ssl->ssl, NULL, 0)) == 0) return 0; buffer = (char*)malloc(len); if (!buffer) { lua_pushnil(L); lua_pushstring(L, "out of memory"); return 2; } SSL_get_peer_finished(ssl->ssl, buffer, len); lua_pushlstring(L, buffer, len); free(buffer); return 1; } /** * Object information -- tostring metamethod */ static int meth_tostring(lua_State *L) { p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); lua_pushfstring(L, "SSL connection: %p%s", ssl, ssl->state == LSEC_STATE_CLOSED ? " (closed)" : ""); return 1; } /** * Add a method in the SSL metatable. */ static int meth_setmethod(lua_State *L) { luaL_getmetatable(L, "SSL:Connection"); lua_pushstring(L, "__index"); lua_gettable(L, -2); lua_pushvalue(L, 1); lua_pushvalue(L, 2); lua_settable(L, -3); return 0; } /** * Return information about the connection. */ static int meth_info(lua_State *L) { int bits = 0; int algbits = 0; char buf[256] = {0}; const SSL_CIPHER *cipher; p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); cipher = SSL_get_current_cipher(ssl->ssl); if (!cipher) return 0; SSL_CIPHER_description(cipher, buf, sizeof(buf)); bits = SSL_CIPHER_get_bits(cipher, &algbits); lua_pushstring(L, buf); lua_pushnumber(L, bits); lua_pushnumber(L, algbits); lua_pushstring(L, SSL_get_version(ssl->ssl)); return 4; } static int sni_cb(SSL *ssl, int *ad, void *arg) { int strict; SSL_CTX *newctx = NULL; SSL_CTX *ctx = SSL_get_SSL_CTX(ssl); lua_State *L = ((p_context)SSL_CTX_get_app_data(ctx))->L; const char *name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); /* No name, use default context */ if (!name) return SSL_TLSEXT_ERR_NOACK; /* Retrieve struct from registry */ luaL_getmetatable(L, "SSL:SNI:Registry"); lua_pushlightuserdata(L, (void*)ssl); lua_gettable(L, -2); /* Strict search? */ lua_pushstring(L, "strict"); lua_gettable(L, -2); strict = lua_toboolean(L, -1); lua_pop(L, 1); /* Search for the name in the map */ lua_pushstring(L, "map"); lua_gettable(L, -2); lua_pushstring(L, name); lua_gettable(L, -2); if (lua_isuserdata(L, -1)) newctx = lsec_checkcontext(L, -1); lua_pop(L, 4); /* Found, use this context */ if (newctx) { p_context pctx = (p_context)SSL_CTX_get_app_data(newctx); pctx->L = L; SSL_set_SSL_CTX(ssl, newctx); return SSL_TLSEXT_ERR_OK; } /* Not found, but use initial context */ if (!strict) return SSL_TLSEXT_ERR_OK; return SSL_TLSEXT_ERR_ALERT_FATAL; } static int meth_sni(lua_State *L) { int strict; SSL_CTX *aux; const char *name; p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); SSL_CTX *ctx = SSL_get_SSL_CTX(ssl->ssl); p_context pctx = (p_context)SSL_CTX_get_app_data(ctx); if (pctx->mode == LSEC_MODE_CLIENT) { name = luaL_checkstring(L, 2); SSL_set_tlsext_host_name(ssl->ssl, name); return 0; } else if (pctx->mode == LSEC_MODE_SERVER) { luaL_checktype(L, 2, LUA_TTABLE); strict = lua_toboolean(L, 3); /* Check if the table contains only (string -> context) */ lua_pushnil(L); while (lua_next(L, 2)) { luaL_checkstring(L, -2); aux = lsec_checkcontext(L, -1); /* Set callback in every context */ SSL_CTX_set_tlsext_servername_callback(aux, sni_cb); /* leave the next key on the stack */ lua_pop(L, 1); } /* Save table in the register */ luaL_getmetatable(L, "SSL:SNI:Registry"); lua_pushlightuserdata(L, (void*)ssl->ssl); lua_newtable(L); lua_pushstring(L, "map"); lua_pushvalue(L, 2); lua_settable(L, -3); lua_pushstring(L, "strict"); lua_pushboolean(L, strict); lua_settable(L, -3); lua_settable(L, -3); /* Set callback in the default context */ SSL_CTX_set_tlsext_servername_callback(ctx, sni_cb); } return 0; } static int meth_getsniname(lua_State *L) { p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); const char *name = SSL_get_servername(ssl->ssl, TLSEXT_NAMETYPE_host_name); if (name) lua_pushstring(L, name); else lua_pushnil(L); return 1; } static int meth_getalpn(lua_State *L) { unsigned len; const unsigned char *data; p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); SSL_get0_alpn_selected(ssl->ssl, &data, &len); if (data == NULL && len == 0) lua_pushnil(L); else lua_pushlstring(L, (const char*)data, len); return 1; } static int meth_copyright(lua_State *L) { lua_pushstring(L, "LuaSec 1.0.2 - Copyright (C) 2006-2021 Bruno Silvestre, UFG" #if defined(WITH_LUASOCKET) "\nLuaSocket 3.0-RC1 - Copyright (C) 2004-2013 Diego Nehab" #endif ); return 1; } #if defined(LSEC_ENABLE_DANE) static int meth_dane(lua_State *L) { int ret; p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); ret = SSL_dane_enable(ssl->ssl, luaL_checkstring(L, 2)); lua_pushboolean(L, (ret > 0)); return 1; } static int meth_tlsa(lua_State *L) { int ret; size_t len; p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection"); uint8_t usage = (uint8_t)luaL_checkinteger(L, 2); uint8_t selector = (uint8_t)luaL_checkinteger(L, 3); uint8_t mtype = (uint8_t)luaL_checkinteger(L, 4); unsigned char *data = (unsigned char*)luaL_checklstring(L, 5, &len); ERR_clear_error(); ret = SSL_dane_tlsa_add(ssl->ssl, usage, selector, mtype, data, len); lua_pushboolean(L, (ret > 0)); return 1; } #endif /*---------------------------------------------------------------------------*/ /** * SSL methods */ static luaL_Reg methods[] = { {"close", meth_close}, {"getalpn", meth_getalpn}, {"getfd", meth_getfd}, {"getfinished", meth_getfinished}, {"getpeercertificate", meth_getpeercertificate}, {"getpeerchain", meth_getpeerchain}, {"getpeerverification", meth_getpeerverification}, {"getpeerfinished", meth_getpeerfinished}, {"getsniname", meth_getsniname}, {"getstats", meth_getstats}, {"setstats", meth_setstats}, {"dirty", meth_dirty}, {"dohandshake", meth_handshake}, {"receive", meth_receive}, {"send", meth_send}, {"settimeout", meth_settimeout}, {"sni", meth_sni}, {"want", meth_want}, #if defined(LSEC_ENABLE_DANE) {"setdane", meth_dane}, {"settlsa", meth_tlsa}, #endif {NULL, NULL} }; /** * SSL metamethods. */ static luaL_Reg meta[] = { {"__close", meth_destroy}, {"__gc", meth_destroy}, {"__tostring", meth_tostring}, {NULL, NULL} }; /** * SSL functions. */ static luaL_Reg funcs[] = { {"compression", meth_compression}, {"create", meth_create}, {"info", meth_info}, {"setfd", meth_setfd}, {"setmethod", meth_setmethod}, {"copyright", meth_copyright}, {NULL, NULL} }; /** * Initialize modules. */ LSEC_API int luaopen_ssl_core(lua_State *L) { #ifndef LSEC_API_OPENSSL_1_1_0 /* Initialize SSL */ if (!SSL_library_init()) { lua_pushstring(L, "unable to initialize SSL library"); lua_error(L); } OpenSSL_add_all_algorithms(); SSL_load_error_strings(); #endif #if defined(WITH_LUASOCKET) /* Initialize internal library */ socket_open(); #endif luaL_newmetatable(L, "SSL:SNI:Registry"); /* Register the functions and tables */ luaL_newmetatable(L, "SSL:Connection"); setfuncs(L, meta); luaL_newlib(L, methods); lua_setfield(L, -2, "__index"); luaL_newlib(L, funcs); lua_pushvalue(L, -1); lua_setglobal(L, "ssl"); lua_pushstring(L, "SOCKET_INVALID"); lua_pushinteger(L, SOCKET_INVALID); lua_rawset(L, -3); return 1; } //------------------------------------------------------------------------------ #if defined(_MSC_VER) /* Empty implementation to allow building with LuaRocks and MS compilers */ LSEC_API int luaopen_ssl(lua_State *L) { lua_pushstring(L, "you should not call this function"); lua_error(L); return 0; } #endif ================================================ FILE: src/luasec/ssl.h ================================================ #ifndef LSEC_SSL_H #define LSEC_SSL_H /*-------------------------------------------------------------------------- * LuaSec 1.0.2 * * Copyright (C) 2006-2021 Bruno Silvestre * *--------------------------------------------------------------------------*/ #include #include "../lua.h" #include "../luasocket/io.h" #include "../luasocket/buffer.h" #include "../luasocket/timeout.h" #include "../luasocket/socket.h" #include "compat.h" #include "context.h" #define LSEC_STATE_NEW 1 #define LSEC_STATE_CONNECTED 2 #define LSEC_STATE_CLOSED 3 #define LSEC_IO_SSL -100 typedef struct t_ssl_ { t_socket sock; t_io io; t_buffer buf; t_timeout tm; SSL *ssl; int state; int error; } t_ssl; typedef t_ssl* p_ssl; LSEC_API int luaopen_ssl_core(lua_State *L); #endif ================================================ FILE: src/luasec/ssl.lua ================================================ ------------------------------------------------------------------------------ -- LuaSec 1.0.2 -- -- Copyright (C) 2006-2021 Bruno Silvestre -- ------------------------------------------------------------------------------ local core = ssl -- We must prevent the contexts to be collected before the connections, -- otherwise the C registry will be cleared. local registry = setmetatable({}, {__mode="k"}) -- -- -- 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 -- -- Convert an array of strings to wire-format -- local function array2wireformat(array) local str = "" for k, v in ipairs(array) do if type(v) ~= "string" then return nil end local len = #v if len == 0 then return nil, "invalid ALPN name (empty string)" elseif len > 255 then return nil, "invalid ALPN name (length > 255)" end str = str .. string.char(len) .. v end if str == "" then return nil, "invalid ALPN list (empty)" end return str end -- -- Convert wire-string format to array -- local function wireformat2array(str) local i = 1 local array = {} while i < #str do local len = str:byte(i) array[#array + 1] = str:sub(i + 1, i + len) i = i + len + 1 end return array end -- -- -- local 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 local certificates = cfg.certificates if not certificates then certificates = { { certificate = cfg.certificate, key = cfg.key, password = cfg.password } } end for _, certificate in ipairs(certificates) do -- Load the key if certificate.key then if certificate.password and type(certificate.password) ~= "function" and type(certificate.password) ~= "string" then return nil, "invalid password type" end succ, msg = context.loadkey(ctx, certificate.key, certificate.password) if not succ then return nil, msg end end -- Load the certificate(s) if certificate.certificate then succ, msg = context.loadcert(ctx, certificate.certificate) if not succ then return nil, msg end if certificate.key and context.checkkey then succ = context.checkkey(ctx) if not succ then return nil, "private key does not match public key" end end 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 SSL ciphers if cfg.ciphers then succ, msg = context.setcipher(ctx, cfg.ciphers) if not succ then return nil, msg end end -- Set SSL cipher suites if cfg.ciphersuites then succ, msg = context.setciphersuites(ctx, cfg.ciphersuites) 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 -- NOTE: Setting DH parameters and elliptic curves needs to come after -- setoptions(), in case the user has specified the single_{dh,ecdh}_use -- options. -- Set DH parameters if cfg.dhparam then if type(cfg.dhparam) ~= "function" then return nil, "invalid DH parameter type" end context.setdhparam(ctx, cfg.dhparam) end -- Set elliptic curves if (not config.algorithms.ec) and (cfg.curve or cfg.curveslist) then return false, "elliptic curves not supported" end if config.capabilities.curves_list and cfg.curveslist then succ, msg = context.setcurveslist(ctx, cfg.curveslist) if not succ then return nil, msg end elseif cfg.curve then succ, msg = context.setcurve(ctx, cfg.curve) if not succ then return nil, msg end end -- Set extra verification options if cfg.verifyext and ctx.setverifyext then succ, msg = optexec(ctx.setverifyext, cfg.verifyext, ctx) if not succ then return nil, msg end end -- ALPN if cfg.mode == "server" and cfg.alpn then if type(cfg.alpn) == "function" then local alpncb = cfg.alpn -- This callback function has to return one value only succ, msg = context.setalpncb(ctx, function(str) local protocols = alpncb(wireformat2array(str)) if type(protocols) == "string" then protocols = { protocols } elseif type(protocols) ~= "table" then return nil end return (array2wireformat(protocols)) -- use "()" to drop error message end) if not succ then return nil, msg end elseif type(cfg.alpn) == "table" then local protocols = cfg.alpn -- check if array is valid before use it succ, msg = array2wireformat(protocols) if not succ then return nil, msg end -- This callback function has to return one value only succ, msg = context.setalpncb(ctx, function() return (array2wireformat(protocols)) -- use "()" to drop error message end) if not succ then return nil, msg end else return nil, "invalid ALPN parameter" end elseif cfg.mode == "client" and cfg.alpn then local alpn if type(cfg.alpn) == "string" then alpn, msg = array2wireformat({ cfg.alpn }) elseif type(cfg.alpn) == "table" then alpn, msg = array2wireformat(cfg.alpn) else return nil, "invalid ALPN parameter" end if not alpn then return nil, msg end succ, msg = context.setalpn(ctx, alpn) if not succ then return nil, msg end end if config.capabilities.dane and cfg.dane then context.setdane(ctx) end return ctx end -- -- -- local 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.SOCKET_INVALID) registry[s] = ctx return s end return nil, msg end -- -- Extract connection information. -- local function info(ssl, field) local str, comp, err, protocol comp, err = core.compression(ssl) if err then return comp, err end -- Avoid parser if field == "compression" then return comp end local info = {compression = comp} str, info.bits, info.algbits, protocol = core.info(ssl) if str then info.cipher, info.protocol, info.key, info.authentication, info.encryption, info.mac = string.match(str, "^(%S+)%s+(%S+)%s+Kx=(%S+)%s+Au=(%S+)%s+Enc=(%S+)%s+Mac=(%S+)") info.export = (string.match(str, "%sexport%s*$") ~= nil) end if protocol then info.protocol = protocol end if field then return info[field] end -- Empty? return ( (next(info)) and info ) end -- -- Set method for SSL connections. -- core.setmethod("info", info) -------------------------------------------------------------------------------- -- Export module -- local _M = { _VERSION = "1.0.2", _COPYRIGHT = core.copyright(), config = config, loadcertificate = x509.load, newcontext = newcontext, wrap = wrap, } return _M ================================================ FILE: src/luasec/x509.c ================================================ /*-------------------------------------------------------------------------- * LuaSec 1.0.2 * * Copyright (C) 2014-2021 Kim Alvefur, Paul Aurich, Tobias Markmann * Matthew Wild, Bruno Silvestre. * *--------------------------------------------------------------------------*/ #include #include #if defined(WIN32) #include #include #else #include #include #include #include #endif #include #include #include #include #include #include #include #include "../lua.h" #include "../lauxlib.h" #include "x509.h" #ifndef LSEC_API_OPENSSL_1_1_0 #define X509_get0_notBefore X509_get_notBefore #define X509_get0_notAfter X509_get_notAfter #define ASN1_STRING_get0_data ASN1_STRING_data #endif static const char* hex_tab = "0123456789abcdef"; /** * Push the certificate on the stack. */ void lsec_pushx509(lua_State* L, X509 *cert) { p_x509 cert_obj = (p_x509)lua_newuserdata(L, sizeof(t_x509)); cert_obj->cert = cert; cert_obj->encode = LSEC_AI5_STRING; luaL_getmetatable(L, "SSL:Certificate"); lua_setmetatable(L, -2); } /** * Return the OpenSSL certificate X509. */ X509* lsec_checkx509(lua_State* L, int idx) { return ((p_x509)luaL_checkudata(L, idx, "SSL:Certificate"))->cert; } /** * Return LuaSec certificate X509 representation. */ p_x509 lsec_checkp_x509(lua_State* L, int idx) { return (p_x509)luaL_checkudata(L, idx, "SSL:Certificate"); } /*---------------------------------------------------------------------------*/ #if defined(LUASEC_INET_NTOP) /* * For WinXP (SP3), set the following preprocessor macros: * LUASEC_INET_NTOP * WINVER=0x0501 * _WIN32_WINNT=0x0501 * NTDDI_VERSION=0x05010300 * * For IPv6 addresses, you need to add IPv6 Protocol to your interface. * */ static const char *inet_ntop(int af, const char *src, char *dst, socklen_t size) { int addrsize; struct sockaddr *addr; struct sockaddr_in addr4; struct sockaddr_in6 addr6; switch (af) { case AF_INET: memset((void*)&addr4, 0, sizeof(addr4)); addr4.sin_family = AF_INET; memcpy((void*)&addr4.sin_addr, src, sizeof(struct in_addr)); addr = (struct sockaddr*)&addr4; addrsize = sizeof(struct sockaddr_in); break; case AF_INET6: memset((void*)&addr6, 0, sizeof(addr6)); addr6.sin6_family = AF_INET6; memcpy((void*)&addr6.sin6_addr, src, sizeof(struct in6_addr)); addr = (struct sockaddr*)&addr6; addrsize = sizeof(struct sockaddr_in6); break; default: return NULL; } if(getnameinfo(addr, addrsize, dst, size, NULL, 0, NI_NUMERICHOST) != 0) return NULL; return dst; } #endif /*---------------------------------------------------------------------------*/ /** * Convert the buffer 'in' to hexadecimal. */ static void to_hex(const char* in, int length, char* out) { int i; for (i = 0; i < length; i++) { out[i*2] = hex_tab[(in[i] >> 4) & 0xF]; out[i*2+1] = hex_tab[(in[i]) & 0xF]; } } /** * Converts the ASN1_OBJECT into a textual representation and put it * on the Lua stack. */ static void push_asn1_objname(lua_State* L, ASN1_OBJECT *object, int no_name) { char buffer[256]; int len = OBJ_obj2txt(buffer, sizeof(buffer), object, no_name); len = (len < sizeof(buffer)) ? len : sizeof(buffer); lua_pushlstring(L, buffer, len); } /** * Push the ASN1 string on the stack. */ static void push_asn1_string(lua_State* L, ASN1_STRING *string, int encode) { int len; unsigned char *data; if (!string) { lua_pushnil(L); return; } switch (encode) { case LSEC_AI5_STRING: lua_pushlstring(L, (char*)ASN1_STRING_get0_data(string), ASN1_STRING_length(string)); break; case LSEC_UTF8_STRING: len = ASN1_STRING_to_UTF8(&data, string); if (len >= 0) { lua_pushlstring(L, (char*)data, len); OPENSSL_free(data); } else lua_pushnil(L); } } /** * Return a human readable time. */ static int push_asn1_time(lua_State *L, const ASN1_UTCTIME *tm) { char *tmp; long size; BIO *out = BIO_new(BIO_s_mem()); ASN1_TIME_print(out, tm); size = BIO_get_mem_data(out, &tmp); lua_pushlstring(L, tmp, size); BIO_free(out); return 1; } /** * Return a human readable IP address. */ static void push_asn1_ip(lua_State *L, ASN1_STRING *string) { int af; char dst[INET6_ADDRSTRLEN]; unsigned char *ip = (unsigned char*)ASN1_STRING_get0_data(string); switch(ASN1_STRING_length(string)) { case 4: af = AF_INET; break; case 16: af = AF_INET6; break; default: lua_pushnil(L); return; } if(inet_ntop(af, ip, dst, INET6_ADDRSTRLEN)) lua_pushstring(L, dst); else lua_pushnil(L); } /** * */ static int push_subtable(lua_State* L, int idx) { lua_pushvalue(L, -1); lua_gettable(L, idx-1); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_newtable(L); lua_pushvalue(L, -2); lua_pushvalue(L, -2); lua_settable(L, idx-3); lua_replace(L, -2); /* Replace key with table */ return 1; } lua_replace(L, -2); /* Replace key with table */ return 0; } /** * Retrieve the general names from the object. */ static int push_x509_name(lua_State* L, X509_NAME *name, int encode) { int i; int n_entries; ASN1_OBJECT *object; X509_NAME_ENTRY *entry; lua_newtable(L); n_entries = X509_NAME_entry_count(name); for (i = 0; i < n_entries; i++) { entry = X509_NAME_get_entry(name, i); object = X509_NAME_ENTRY_get_object(entry); lua_newtable(L); push_asn1_objname(L, object, 1); lua_setfield(L, -2, "oid"); push_asn1_objname(L, object, 0); lua_setfield(L, -2, "name"); push_asn1_string(L, X509_NAME_ENTRY_get_data(entry), encode); lua_setfield(L, -2, "value"); lua_rawseti(L, -2, i+1); } return 1; } /*---------------------------------------------------------------------------*/ /** * Retrieve the Subject from the certificate. */ static int meth_subject(lua_State* L) { p_x509 px = lsec_checkp_x509(L, 1); return push_x509_name(L, X509_get_subject_name(px->cert), px->encode); } /** * Retrieve the Issuer from the certificate. */ static int meth_issuer(lua_State* L) { p_x509 px = lsec_checkp_x509(L, 1); return push_x509_name(L, X509_get_issuer_name(px->cert), px->encode); } /** * Retrieve the extensions from the certificate. */ int meth_extensions(lua_State* L) { int j; int i = -1; int n_general_names; OTHERNAME *otherName; X509_EXTENSION *extension; GENERAL_NAME *general_name; STACK_OF(GENERAL_NAME) *values; p_x509 px = lsec_checkp_x509(L, 1); X509 *peer = px->cert; /* Return (ret) */ lua_newtable(L); while ((i = X509_get_ext_by_NID(peer, NID_subject_alt_name, i)) != -1) { extension = X509_get_ext(peer, i); if (extension == NULL) break; values = X509V3_EXT_d2i(extension); if (values == NULL) break; /* Push ret[oid] */ push_asn1_objname(L, X509_EXTENSION_get_object(extension), 1); push_subtable(L, -2); /* Set ret[oid].name = name */ push_asn1_objname(L, X509_EXTENSION_get_object(extension), 0); lua_setfield(L, -2, "name"); n_general_names = sk_GENERAL_NAME_num(values); for (j = 0; j < n_general_names; j++) { general_name = sk_GENERAL_NAME_value(values, j); switch (general_name->type) { case GEN_OTHERNAME: otherName = general_name->d.otherName; push_asn1_objname(L, otherName->type_id, 1); if (push_subtable(L, -2)) { push_asn1_objname(L, otherName->type_id, 0); lua_setfield(L, -2, "name"); } push_asn1_string(L, otherName->value->value.asn1_string, px->encode); lua_rawseti(L, -2, lua_rawlen(L, -2) + 1); lua_pop(L, 1); break; case GEN_DNS: lua_pushstring(L, "dNSName"); push_subtable(L, -2); push_asn1_string(L, general_name->d.dNSName, px->encode); lua_rawseti(L, -2, lua_rawlen(L, -2) + 1); lua_pop(L, 1); break; case GEN_EMAIL: lua_pushstring(L, "rfc822Name"); push_subtable(L, -2); push_asn1_string(L, general_name->d.rfc822Name, px->encode); lua_rawseti(L, -2, lua_rawlen(L, -2) + 1); lua_pop(L, 1); break; case GEN_URI: lua_pushstring(L, "uniformResourceIdentifier"); push_subtable(L, -2); push_asn1_string(L, general_name->d.uniformResourceIdentifier, px->encode); lua_rawseti(L, -2, lua_rawlen(L, -2)+1); lua_pop(L, 1); break; case GEN_IPADD: lua_pushstring(L, "iPAddress"); push_subtable(L, -2); push_asn1_ip(L, general_name->d.iPAddress); lua_rawseti(L, -2, lua_rawlen(L, -2)+1); lua_pop(L, 1); break; case GEN_X400: /* x400Address */ /* not supported */ break; case GEN_DIRNAME: /* directoryName */ /* not supported */ break; case GEN_EDIPARTY: /* ediPartyName */ /* not supported */ break; case GEN_RID: /* registeredID */ /* not supported */ break; } GENERAL_NAME_free(general_name); } sk_GENERAL_NAME_free(values); lua_pop(L, 1); /* ret[oid] */ i++; /* Next extension */ } return 1; } /** * Convert the certificate to PEM format. */ static int meth_pem(lua_State* L) { char* data; long bytes; X509* cert = lsec_checkx509(L, 1); BIO *bio = BIO_new(BIO_s_mem()); if (!PEM_write_bio_X509(bio, cert)) { lua_pushnil(L); return 1; } bytes = BIO_get_mem_data(bio, &data); if (bytes > 0) lua_pushlstring(L, data, bytes); else lua_pushnil(L); BIO_free(bio); return 1; } /** * Extract public key in PEM format. */ static int meth_pubkey(lua_State* L) { char* data; long bytes; int ret = 1; X509* cert = lsec_checkx509(L, 1); BIO *bio = BIO_new(BIO_s_mem()); EVP_PKEY *pkey = X509_get_pubkey(cert); if(PEM_write_bio_PUBKEY(bio, pkey)) { bytes = BIO_get_mem_data(bio, &data); if (bytes > 0) { lua_pushlstring(L, data, bytes); switch(EVP_PKEY_base_id(pkey)) { case EVP_PKEY_RSA: lua_pushstring(L, "RSA"); break; case EVP_PKEY_DSA: lua_pushstring(L, "DSA"); break; case EVP_PKEY_DH: lua_pushstring(L, "DH"); break; case EVP_PKEY_EC: lua_pushstring(L, "EC"); break; default: lua_pushstring(L, "Unknown"); break; } lua_pushinteger(L, EVP_PKEY_bits(pkey)); ret = 3; } else lua_pushnil(L); } else lua_pushnil(L); /* Cleanup */ BIO_free(bio); EVP_PKEY_free(pkey); return ret; } /** * Compute the fingerprint. */ static int meth_digest(lua_State* L) { unsigned int bytes; const EVP_MD *digest = NULL; unsigned char buffer[EVP_MAX_MD_SIZE]; char hex_buffer[EVP_MAX_MD_SIZE*2]; X509 *cert = lsec_checkx509(L, 1); const char *str = luaL_optstring(L, 2, NULL); if (!str) digest = EVP_sha1(); else { if (!strcmp(str, "sha1")) digest = EVP_sha1(); else if (!strcmp(str, "sha256")) digest = EVP_sha256(); else if (!strcmp(str, "sha512")) digest = EVP_sha512(); } if (!digest) { lua_pushnil(L); lua_pushfstring(L, "digest algorithm not supported (%s)", str); return 2; } if (!X509_digest(cert, digest, buffer, &bytes)) { lua_pushnil(L); lua_pushfstring(L, "error processing the certificate (%s)", ERR_reason_error_string(ERR_get_error())); return 2; } to_hex((char*)buffer, bytes, hex_buffer); lua_pushlstring(L, hex_buffer, bytes*2); return 1; } /** * Check if the certificate is valid in a given time. */ static int meth_valid_at(lua_State* L) { int nb, na; X509* cert = lsec_checkx509(L, 1); time_t time = luaL_checkinteger(L, 2); nb = X509_cmp_time(X509_get0_notBefore(cert), &time); time -= 1; na = X509_cmp_time(X509_get0_notAfter(cert), &time); lua_pushboolean(L, nb == -1 && na == 1); return 1; } /** * Return the serial number. */ static int meth_serial(lua_State *L) { char *tmp; BIGNUM *bn; ASN1_INTEGER *serial; X509* cert = lsec_checkx509(L, 1); serial = X509_get_serialNumber(cert); bn = ASN1_INTEGER_to_BN(serial, NULL); tmp = BN_bn2hex(bn); lua_pushstring(L, tmp); BN_free(bn); OPENSSL_free(tmp); return 1; } /** * Return not before date. */ static int meth_notbefore(lua_State *L) { X509* cert = lsec_checkx509(L, 1); return push_asn1_time(L, X509_get0_notBefore(cert)); } /** * Return not after date. */ static int meth_notafter(lua_State *L) { X509* cert = lsec_checkx509(L, 1); return push_asn1_time(L, X509_get0_notAfter(cert)); } /** * Check if this certificate issued some other certificate */ static int meth_issued(lua_State* L) { int ret, i, len; X509_STORE_CTX* ctx = NULL; X509_STORE* root = NULL; STACK_OF(X509)* chain = NULL; X509* issuer = lsec_checkx509(L, 1); X509* subject = lsec_checkx509(L, 2); X509* cert = NULL; len = lua_gettop(L); /* Check that all arguments are certificates */ for (i = 3; i <= len; i++) { lsec_checkx509(L, i); } /* Before allocating things that require freeing afterwards */ chain = sk_X509_new_null(); ctx = X509_STORE_CTX_new(); root = X509_STORE_new(); if (ctx == NULL || root == NULL) { lua_pushnil(L); lua_pushstring(L, "X509_STORE_new() or X509_STORE_CTX_new() error"); ret = 2; goto cleanup; } ret = X509_STORE_add_cert(root, issuer); if(!ret) { lua_pushnil(L); lua_pushstring(L, "X509_STORE_add_cert() error"); ret = 2; goto cleanup; } for (i = 3; i <= len && lua_isuserdata(L, i); i++) { cert = lsec_checkx509(L, i); sk_X509_push(chain, cert); } ret = X509_STORE_CTX_init(ctx, root, subject, chain); if(!ret) { lua_pushnil(L); lua_pushstring(L, "X509_STORE_CTX_init() error"); ret = 2; goto cleanup; } /* Actual verification */ if (X509_verify_cert(ctx) <= 0) { ret = X509_STORE_CTX_get_error(ctx); lua_pushnil(L); lua_pushstring(L, X509_verify_cert_error_string(ret)); ret = 2; } else { lua_pushboolean(L, 1); ret = 1; } cleanup: if (ctx != NULL) { X509_STORE_CTX_free(ctx); } if (chain != NULL) { X509_STORE_free(root); } sk_X509_free(chain); return ret; } /** * Collect X509 objects. */ static int meth_destroy(lua_State* L) { p_x509 px = lsec_checkp_x509(L, 1); if (px->cert) { X509_free(px->cert); px->cert = NULL; } return 0; } static int meth_tostring(lua_State *L) { X509* cert = lsec_checkx509(L, 1); lua_pushfstring(L, "X509 certificate: %p", cert); return 1; } /** * Set the encode for ASN.1 string. */ static int meth_set_encode(lua_State* L) { int succ = 0; p_x509 px = lsec_checkp_x509(L, 1); const char *enc = luaL_checkstring(L, 2); if (strncmp(enc, "ai5", 3) == 0) { succ = 1; px->encode = LSEC_AI5_STRING; } else if (strncmp(enc, "utf8", 4) == 0) { succ = 1; px->encode = LSEC_UTF8_STRING; } lua_pushboolean(L, succ); return 1; } /** * Get signature name. */ static int meth_get_signature_name(lua_State* L) { p_x509 px = lsec_checkp_x509(L, 1); int nid = X509_get_signature_nid(px->cert); const char *name = OBJ_nid2sn(nid); if (!name) lua_pushnil(L); else lua_pushstring(L, name); return 1; } /*---------------------------------------------------------------------------*/ static int load_cert(lua_State* L) { X509 *cert; size_t bytes; const char* data; BIO *bio = BIO_new(BIO_s_mem()); data = luaL_checklstring(L, 1, &bytes); BIO_write(bio, data, bytes); cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); if (cert) lsec_pushx509(L, cert); else lua_pushnil(L); BIO_free(bio); return 1; } /*---------------------------------------------------------------------------*/ /** * Certificate methods. */ static luaL_Reg methods[] = { {"digest", meth_digest}, {"setencode", meth_set_encode}, {"extensions", meth_extensions}, {"getsignaturename", meth_get_signature_name}, {"issuer", meth_issuer}, {"notbefore", meth_notbefore}, {"notafter", meth_notafter}, {"issued", meth_issued}, {"pem", meth_pem}, {"pubkey", meth_pubkey}, {"serial", meth_serial}, {"subject", meth_subject}, {"validat", meth_valid_at}, {NULL, NULL} }; /** * X509 metamethods. */ static luaL_Reg meta[] = { {"__close", meth_destroy}, {"__gc", meth_destroy}, {"__tostring", meth_tostring}, {NULL, NULL} }; /** * X509 functions. */ static luaL_Reg funcs[] = { {"load", load_cert}, {NULL, NULL} }; /*--------------------------------------------------------------------------*/ LSEC_API int luaopen_ssl_x509(lua_State *L) { /* Register the functions and tables */ luaL_newmetatable(L, "SSL:Certificate"); setfuncs(L, meta); luaL_newlib(L, methods); lua_setfield(L, -2, "__index"); luaL_newlib(L, funcs); lua_pushvalue(L, -1); lua_setglobal(L, "x509"); return 1; } ================================================ FILE: src/luasec/x509.h ================================================ /*-------------------------------------------------------------------------- * LuaSec 1.0.2 * * Copyright (C) 2014-2021 Kim Alvefur, Paul Aurich, Tobias Markmann * Matthew Wild, Bruno Silvestre. * *--------------------------------------------------------------------------*/ #ifndef LSEC_X509_H #define LSEC_X509_H #include #include "../lua.h" #include "compat.h" /* We do not support UniversalString nor BMPString as ASN.1 String types */ enum { LSEC_AI5_STRING, LSEC_UTF8_STRING }; typedef struct t_x509_ { X509 *cert; int encode; } t_x509; typedef t_x509* p_x509; void lsec_pushx509(lua_State* L, X509* cert); X509* lsec_checkx509(lua_State* L, int idx); LSEC_API int luaopen_ssl_x509(lua_State *L); #endif ================================================ FILE: src/luasocket/auxiliar.c ================================================ /*=========================================================================*\ * Auxiliar routines for class hierarchy manipulation * LuaSocket toolkit \*=========================================================================*/ #include "luasocket.h" #include "auxiliar.h" #include #include /*-------------------------------------------------------------------------*\ * Initializes the module \*-------------------------------------------------------------------------*/ int auxiliar_open(lua_State *L) { (void) L; return 0; } /*-------------------------------------------------------------------------*\ * Creates a new class with given methods * Methods whose names start with __ are passed directly to the metatable. \*-------------------------------------------------------------------------*/ void auxiliar_newclass(lua_State *L, const char *classname, luaL_Reg *func) { luaL_newmetatable(L, classname); /* mt */ /* create __index table to place methods */ lua_pushstring(L, "__index"); /* mt,"__index" */ lua_newtable(L); /* mt,"__index",it */ /* put class name into class metatable */ lua_pushstring(L, "class"); /* mt,"__index",it,"class" */ lua_pushstring(L, classname); /* mt,"__index",it,"class",classname */ lua_rawset(L, -3); /* mt,"__index",it */ /* pass all methods that start with _ to the metatable, and all others * to the index table */ for (; func->name; func++) { /* mt,"__index",it */ lua_pushstring(L, func->name); lua_pushcfunction(L, func->func); lua_rawset(L, func->name[0] == '_' ? -5: -3); } lua_rawset(L, -3); /* mt */ lua_pop(L, 1); } /*-------------------------------------------------------------------------*\ * Prints the value of a class in a nice way \*-------------------------------------------------------------------------*/ int auxiliar_tostring(lua_State *L) { char buf[32]; if (!lua_getmetatable(L, 1)) goto error; lua_pushstring(L, "__index"); lua_gettable(L, -2); if (!lua_istable(L, -1)) goto error; lua_pushstring(L, "class"); lua_gettable(L, -2); if (!lua_isstring(L, -1)) goto error; sprintf(buf, "%p", lua_touserdata(L, 1)); lua_pushfstring(L, "%s: %s", lua_tostring(L, -1), buf); return 1; error: lua_pushstring(L, "invalid object passed to 'auxiliar.c:__tostring'"); lua_error(L); return 1; } /*-------------------------------------------------------------------------*\ * Insert class into group \*-------------------------------------------------------------------------*/ void auxiliar_add2group(lua_State *L, const char *classname, const char *groupname) { luaL_getmetatable(L, classname); lua_pushstring(L, groupname); lua_pushboolean(L, 1); lua_rawset(L, -3); lua_pop(L, 1); } /*-------------------------------------------------------------------------*\ * Make sure argument is a boolean \*-------------------------------------------------------------------------*/ int auxiliar_checkboolean(lua_State *L, int objidx) { if (!lua_isboolean(L, objidx)) auxiliar_typeerror(L, objidx, lua_typename(L, LUA_TBOOLEAN)); return lua_toboolean(L, objidx); } /*-------------------------------------------------------------------------*\ * Return userdata pointer if object belongs to a given class, abort with * error otherwise \*-------------------------------------------------------------------------*/ void *auxiliar_checkclass(lua_State *L, const char *classname, int objidx) { void *data = auxiliar_getclassudata(L, classname, objidx); if (!data) { char msg[45]; sprintf(msg, "%.35s expected", classname); luaL_argerror(L, objidx, msg); } return data; } /*-------------------------------------------------------------------------*\ * Return userdata pointer if object belongs to a given group, abort with * error otherwise \*-------------------------------------------------------------------------*/ void *auxiliar_checkgroup(lua_State *L, const char *groupname, int objidx) { void *data = auxiliar_getgroupudata(L, groupname, objidx); if (!data) { char msg[45]; sprintf(msg, "%.35s expected", groupname); luaL_argerror(L, objidx, msg); } return data; } /*-------------------------------------------------------------------------*\ * Set object class \*-------------------------------------------------------------------------*/ void auxiliar_setclass(lua_State *L, const char *classname, int objidx) { luaL_getmetatable(L, classname); if (objidx < 0) objidx--; lua_setmetatable(L, objidx); } /*-------------------------------------------------------------------------*\ * Get a userdata pointer if object belongs to a given group. Return NULL * otherwise \*-------------------------------------------------------------------------*/ void *auxiliar_getgroupudata(lua_State *L, const char *groupname, int objidx) { if (!lua_getmetatable(L, objidx)) return NULL; lua_pushstring(L, groupname); lua_rawget(L, -2); if (lua_isnil(L, -1)) { lua_pop(L, 2); return NULL; } else { lua_pop(L, 2); return lua_touserdata(L, objidx); } } /*-------------------------------------------------------------------------*\ * Get a userdata pointer if object belongs to a given class. Return NULL * otherwise \*-------------------------------------------------------------------------*/ void *auxiliar_getclassudata(lua_State *L, const char *classname, int objidx) { return luaL_testudata(L, objidx, classname); } /*-------------------------------------------------------------------------*\ * Throws error when argument does not have correct type. * Used to be part of lauxlib in Lua 5.1, was dropped from 5.2. \*-------------------------------------------------------------------------*/ int auxiliar_typeerror (lua_State *L, int narg, const char *tname) { const char *msg = lua_pushfstring(L, "%s expected, got %s", tname, luaL_typename(L, narg)); return luaL_argerror(L, narg, msg); } ================================================ FILE: src/luasocket/auxiliar.h ================================================ #ifndef AUXILIAR_H #define AUXILIAR_H /*=========================================================================*\ * Auxiliar routines for class hierarchy manipulation * LuaSocket toolkit (but completely independent of other LuaSocket modules) * * A LuaSocket class is a name associated with Lua metatables. A LuaSocket * group is a name associated with a class. A class can belong to any number * of groups. This module provides the functionality to: * * - create new classes * - add classes to groups * - set the class of objects * - check if an object belongs to a given class or group * - get the userdata associated to objects * - print objects in a pretty way * * LuaSocket class names follow the convention {}. Modules * can define any number of classes and groups. The module tcp.c, for * example, defines the classes tcp{master}, tcp{client} and tcp{server} and * the groups tcp{client,server} and tcp{any}. Module functions can then * perform type-checking on their arguments by either class or group. * * LuaSocket metatables define the __index metamethod as being a table. This * table has one field for each method supported by the class, and a field * "class" with the class name. * * The mapping from class name to the corresponding metatable and the * reverse mapping are done using lauxlib. \*=========================================================================*/ #include "luasocket.h" #ifndef _WIN32 #pragma GCC visibility push(hidden) #endif int auxiliar_open(lua_State *L); void auxiliar_newclass(lua_State *L, const char *classname, luaL_Reg *func); int auxiliar_tostring(lua_State *L); void auxiliar_add2group(lua_State *L, const char *classname, const char *group); int auxiliar_checkboolean(lua_State *L, int objidx); void *auxiliar_checkclass(lua_State *L, const char *classname, int objidx); void *auxiliar_checkgroup(lua_State *L, const char *groupname, int objidx); void auxiliar_setclass(lua_State *L, const char *classname, int objidx); void *auxiliar_getgroupudata(lua_State *L, const char *groupname, int objidx); void *auxiliar_getclassudata(lua_State *L, const char *groupname, int objidx); int auxiliar_typeerror(lua_State *L, int narg, const char *tname); #ifndef _WIN32 #pragma GCC visibility pop #endif #endif /* AUXILIAR_H */ ================================================ FILE: src/luasocket/buffer.c ================================================ /*=========================================================================*\ * Input/Output interface for Lua programs * LuaSocket toolkit \*=========================================================================*/ #include "luasocket.h" #include "buffer.h" /*=========================================================================*\ * Internal function prototypes \*=========================================================================*/ static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b); static int recvline(p_buffer buf, luaL_Buffer *b); static int recvall(p_buffer buf, luaL_Buffer *b); static int buffer_get(p_buffer buf, const char **data, size_t *count); static void buffer_skip(p_buffer buf, size_t count); static int sendraw(p_buffer buf, const char *data, size_t count, size_t *sent); /* min and max macros */ #ifndef MIN #define MIN(x, y) ((x) < (y) ? x : y) #endif #ifndef MAX #define MAX(x, y) ((x) > (y) ? x : y) #endif /*=========================================================================*\ * Exported functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ int buffer_open(lua_State *L) { (void) L; return 0; } /*-------------------------------------------------------------------------*\ * Initializes C structure \*-------------------------------------------------------------------------*/ void buffer_init(p_buffer buf, p_io io, p_timeout tm) { buf->first = buf->last = 0; buf->io = io; buf->tm = tm; buf->received = buf->sent = 0; buf->birthday = timeout_gettime(); } /*-------------------------------------------------------------------------*\ * object:getstats() interface \*-------------------------------------------------------------------------*/ int buffer_meth_getstats(lua_State *L, p_buffer buf) { lua_pushnumber(L, (lua_Number) buf->received); lua_pushnumber(L, (lua_Number) buf->sent); lua_pushnumber(L, timeout_gettime() - buf->birthday); return 3; } /*-------------------------------------------------------------------------*\ * object:setstats() interface \*-------------------------------------------------------------------------*/ int buffer_meth_setstats(lua_State *L, p_buffer buf) { buf->received = (long) luaL_optnumber(L, 2, (lua_Number) buf->received); buf->sent = (long) luaL_optnumber(L, 3, (lua_Number) buf->sent); if (lua_isnumber(L, 4)) buf->birthday = timeout_gettime() - lua_tonumber(L, 4); lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * object:send() interface \*-------------------------------------------------------------------------*/ int buffer_meth_send(lua_State *L, p_buffer buf) { int top = lua_gettop(L); int err = IO_DONE; size_t size = 0, sent = 0; const char *data = luaL_checklstring(L, 2, &size); long start = (long) luaL_optnumber(L, 3, 1); long end = (long) luaL_optnumber(L, 4, -1); timeout_markstart(buf->tm); if (start < 0) start = (long) (size+start+1); if (end < 0) end = (long) (size+end+1); if (start < 1) start = (long) 1; if (end > (long) size) end = (long) size; if (start <= end) err = sendraw(buf, data+start-1, end-start+1, &sent); /* check if there was an error */ if (err != IO_DONE) { lua_pushnil(L); lua_pushstring(L, buf->io->error(buf->io->ctx, err)); lua_pushnumber(L, (lua_Number) (sent+start-1)); } else { lua_pushnumber(L, (lua_Number) (sent+start-1)); lua_pushnil(L); lua_pushnil(L); } #ifdef LUASOCKET_DEBUG /* push time elapsed during operation as the last return value */ lua_pushnumber(L, timeout_gettime() - timeout_getstart(buf->tm)); #endif return lua_gettop(L) - top; } /*-------------------------------------------------------------------------*\ * object:receive() interface \*-------------------------------------------------------------------------*/ int buffer_meth_receive(lua_State *L, p_buffer buf) { int err = IO_DONE, top = lua_gettop(L); luaL_Buffer b; size_t size; const char *part = luaL_optlstring(L, 3, "", &size); timeout_markstart(buf->tm); /* initialize buffer with optional extra prefix * (useful for concatenating previous partial results) */ luaL_buffinit(L, &b); luaL_addlstring(&b, part, size); /* receive new patterns */ if (!lua_isnumber(L, 2)) { const char *p= luaL_optstring(L, 2, "*l"); if (p[0] == '*' && p[1] == 'l') err = recvline(buf, &b); else if (p[0] == '*' && p[1] == 'a') err = recvall(buf, &b); else luaL_argcheck(L, 0, 2, "invalid receive pattern"); /* get a fixed number of bytes (minus what was already partially * received) */ } else { double n = lua_tonumber(L, 2); size_t wanted = (size_t) n; luaL_argcheck(L, n >= 0, 2, "invalid receive pattern"); if (size == 0 || wanted > size) err = recvraw(buf, wanted-size, &b); } /* check if there was an error */ if (err != IO_DONE) { /* we can't push anyting in the stack before pushing the * contents of the buffer. this is the reason for the complication */ luaL_pushresult(&b); lua_pushstring(L, buf->io->error(buf->io->ctx, err)); lua_pushvalue(L, -2); lua_pushnil(L); lua_replace(L, -4); } else { luaL_pushresult(&b); lua_pushnil(L); lua_pushnil(L); } #ifdef LUASOCKET_DEBUG /* push time elapsed during operation as the last return value */ lua_pushnumber(L, timeout_gettime() - timeout_getstart(buf->tm)); #endif return lua_gettop(L) - top; } /*-------------------------------------------------------------------------*\ * Determines if there is any data in the read buffer \*-------------------------------------------------------------------------*/ int buffer_isempty(p_buffer buf) { return buf->first >= buf->last; } /*=========================================================================*\ * Internal functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Sends a block of data (unbuffered) \*-------------------------------------------------------------------------*/ #define STEPSIZE 8192 static int sendraw(p_buffer buf, const char *data, size_t count, size_t *sent) { p_io io = buf->io; p_timeout tm = buf->tm; size_t total = 0; int err = IO_DONE; while (total < count && err == IO_DONE) { size_t done = 0; size_t step = (count-total <= STEPSIZE)? count-total: STEPSIZE; err = io->send(io->ctx, data+total, step, &done, tm); total += done; } *sent = total; buf->sent += total; return err; } /*-------------------------------------------------------------------------*\ * Reads a fixed number of bytes (buffered) \*-------------------------------------------------------------------------*/ static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b) { int err = IO_DONE; size_t total = 0; while (err == IO_DONE) { size_t count; const char *data; err = buffer_get(buf, &data, &count); count = MIN(count, wanted - total); luaL_addlstring(b, data, count); buffer_skip(buf, count); total += count; if (total >= wanted) break; } return err; } /*-------------------------------------------------------------------------*\ * Reads everything until the connection is closed (buffered) \*-------------------------------------------------------------------------*/ static int recvall(p_buffer buf, luaL_Buffer *b) { int err = IO_DONE; size_t total = 0; while (err == IO_DONE) { const char *data; size_t count; err = buffer_get(buf, &data, &count); total += count; luaL_addlstring(b, data, count); buffer_skip(buf, count); } if (err == IO_CLOSED) { if (total > 0) return IO_DONE; else return IO_CLOSED; } else return err; } /*-------------------------------------------------------------------------*\ * Reads a line terminated by a CR LF pair or just by a LF. The CR and LF * are not returned by the function and are discarded from the buffer \*-------------------------------------------------------------------------*/ static int recvline(p_buffer buf, luaL_Buffer *b) { int err = IO_DONE; while (err == IO_DONE) { size_t count, pos; const char *data; err = buffer_get(buf, &data, &count); pos = 0; while (pos < count && data[pos] != '\n') { /* we ignore all \r's */ if (data[pos] != '\r') luaL_addchar(b, data[pos]); pos++; } if (pos < count) { /* found '\n' */ buffer_skip(buf, pos+1); /* skip '\n' too */ break; /* we are done */ } else /* reached the end of the buffer */ buffer_skip(buf, pos); } return err; } /*-------------------------------------------------------------------------*\ * Skips a given number of bytes from read buffer. No data is read from the * transport layer \*-------------------------------------------------------------------------*/ static void buffer_skip(p_buffer buf, size_t count) { buf->received += count; buf->first += count; if (buffer_isempty(buf)) buf->first = buf->last = 0; } /*-------------------------------------------------------------------------*\ * Return any data available in buffer, or get more data from transport layer * if buffer is empty \*-------------------------------------------------------------------------*/ static int buffer_get(p_buffer buf, const char **data, size_t *count) { int err = IO_DONE; p_io io = buf->io; p_timeout tm = buf->tm; if (buffer_isempty(buf)) { size_t got; err = io->recv(io->ctx, buf->data, BUF_SIZE, &got, tm); buf->first = 0; buf->last = got; } *count = buf->last - buf->first; *data = buf->data + buf->first; return err; } ================================================ FILE: src/luasocket/buffer.h ================================================ #ifndef BUF_H #define BUF_H /*=========================================================================*\ * Input/Output interface for Lua programs * LuaSocket toolkit * * Line patterns require buffering. Reading one character at a time involves * too many system calls and is very slow. This module implements the * LuaSocket interface for input/output on connected objects, as seen by * Lua programs. * * Input is buffered. Output is *not* buffered because there was no simple * way of making sure the buffered output data would ever be sent. * * The module is built on top of the I/O abstraction defined in io.h and the * timeout management is done with the timeout.h interface. \*=========================================================================*/ #include "luasocket.h" #include "io.h" #include "timeout.h" /* buffer size in bytes */ #define BUF_SIZE 8192 /* buffer control structure */ typedef struct t_buffer_ { double birthday; /* throttle support info: creation time, */ size_t sent, received; /* bytes sent, and bytes received */ p_io io; /* IO driver used for this buffer */ p_timeout tm; /* timeout management for this buffer */ size_t first, last; /* index of first and last bytes of stored data */ char data[BUF_SIZE]; /* storage space for buffer data */ } t_buffer; typedef t_buffer *p_buffer; #ifndef _WIN32 #pragma GCC visibility push(hidden) #endif int buffer_open(lua_State *L); void buffer_init(p_buffer buf, p_io io, p_timeout tm); int buffer_meth_getstats(lua_State *L, p_buffer buf); int buffer_meth_setstats(lua_State *L, p_buffer buf); int buffer_meth_send(lua_State *L, p_buffer buf); int buffer_meth_receive(lua_State *L, p_buffer buf); int buffer_isempty(p_buffer buf); #ifndef _WIN32 #pragma GCC visibility pop #endif #endif /* BUF_H */ ================================================ FILE: src/luasocket/compat.c ================================================ #include "luasocket.h" #include "compat.h" #if LUA_VERSION_NUM==501 /* ** Adapted from Lua 5.2 */ void luasocket_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { luaL_checkstack(L, nup+1, "too many upvalues"); for (; l->name != NULL; l++) { /* fill the table with given functions */ int i; lua_pushstring(L, l->name); for (i = 0; i < nup; i++) /* copy upvalues to the top */ lua_pushvalue(L, -(nup+1)); lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ lua_settable(L, -(nup + 3)); } lua_pop(L, nup); /* remove upvalues */ } /* ** Duplicated from Lua 5.2 */ void *luasocket_testudata (lua_State *L, int ud, const char *tname) { void *p = lua_touserdata(L, ud); if (p != NULL) { /* value is a userdata? */ if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ luaL_getmetatable(L, tname); /* get correct metatable */ if (!lua_rawequal(L, -1, -2)) /* not the same? */ p = NULL; /* value is a userdata with wrong metatable */ lua_pop(L, 2); /* remove both metatables */ return p; } } return NULL; /* value is not a userdata with a metatable */ } #endif ================================================ FILE: src/luasocket/compat.h ================================================ #ifndef COMPAT_H #define COMPAT_H #ifndef _WIN32 #pragma GCC visibility push(hidden) #endif void luasocket_setfuncs (lua_State *L, const luaL_Reg *l, int nup); void *luasocket_testudata ( lua_State *L, int arg, const char *tname); #ifndef _WIN32 #pragma GCC visibility pop #endif #define luaL_setfuncs luasocket_setfuncs #define luaL_testudata luasocket_testudata #endif ================================================ FILE: src/luasocket/except.c ================================================ /*=========================================================================*\ * Simple exception support * LuaSocket toolkit \*=========================================================================*/ #include "luasocket.h" #include "except.h" #include #if LUA_VERSION_NUM < 502 #define lua_pcallk(L, na, nr, err, ctx, cont) \ (((void)ctx),((void)cont),lua_pcall(L, na, nr, err)) #endif #if LUA_VERSION_NUM < 503 typedef int lua_KContext; #endif /*=========================================================================*\ * Internal function prototypes. \*=========================================================================*/ static int global_protect(lua_State *L); static int global_newtry(lua_State *L); static int protected_(lua_State *L); static int finalize(lua_State *L); static int do_nothing(lua_State *L); /* except functions */ static luaL_Reg func[] = { {"newtry", global_newtry}, {"protect", global_protect}, {NULL, NULL} }; /*-------------------------------------------------------------------------*\ * Try factory \*-------------------------------------------------------------------------*/ static void wrap(lua_State *L) { lua_createtable(L, 1, 0); lua_pushvalue(L, -2); lua_rawseti(L, -2, 1); lua_pushvalue(L, lua_upvalueindex(1)); lua_setmetatable(L, -2); } static int finalize(lua_State *L) { if (!lua_toboolean(L, 1)) { lua_pushvalue(L, lua_upvalueindex(2)); lua_call(L, 0, 0); lua_settop(L, 2); wrap(L); lua_error(L); return 0; } else return lua_gettop(L); } static int do_nothing(lua_State *L) { (void) L; return 0; } static int global_newtry(lua_State *L) { lua_settop(L, 1); if (lua_isnil(L, 1)) lua_pushcfunction(L, do_nothing); lua_pushvalue(L, lua_upvalueindex(1)); lua_insert(L, -2); lua_pushcclosure(L, finalize, 2); return 1; } /*-------------------------------------------------------------------------*\ * Protect factory \*-------------------------------------------------------------------------*/ static int unwrap(lua_State *L) { if (lua_istable(L, -1) && lua_getmetatable(L, -1)) { int r = lua_rawequal(L, -1, lua_upvalueindex(1)); lua_pop(L, 1); if (r) { lua_pushnil(L); lua_rawgeti(L, -2, 1); return 1; } } return 0; } static int protected_finish(lua_State *L, int status, lua_KContext ctx) { (void)ctx; if (status != 0 && status != LUA_YIELD) { if (unwrap(L)) return 2; else return lua_error(L); } else return lua_gettop(L); } #if LUA_VERSION_NUM == 502 static int protected_cont(lua_State *L) { int ctx = 0; int status = lua_getctx(L, &ctx); return protected_finish(L, status, ctx); } #else #define protected_cont protected_finish #endif static int protected_(lua_State *L) { int status; lua_pushvalue(L, lua_upvalueindex(2)); lua_insert(L, 1); status = lua_pcallk(L, lua_gettop(L) - 1, LUA_MULTRET, 0, 0, protected_cont); return protected_finish(L, status, 0); } static int global_protect(lua_State *L) { lua_settop(L, 1); lua_pushvalue(L, lua_upvalueindex(1)); lua_insert(L, 1); lua_pushcclosure(L, protected_, 2); return 1; } /*-------------------------------------------------------------------------*\ * Init module \*-------------------------------------------------------------------------*/ int except_open(lua_State *L) { lua_newtable(L); /* metatable for wrapped exceptions */ lua_pushboolean(L, 0); lua_setfield(L, -2, "__metatable"); luaL_setfuncs(L, func, 1); return 0; } ================================================ FILE: src/luasocket/except.h ================================================ #ifndef EXCEPT_H #define EXCEPT_H /*=========================================================================*\ * Exception control * LuaSocket toolkit (but completely independent from other modules) * * This provides support for simple exceptions in Lua. During the * development of the HTTP/FTP/SMTP support, it became aparent that * error checking was taking a substantial amount of the coding. These * function greatly simplify the task of checking errors. * * The main idea is that functions should return nil as their first return * values when they find an error, and return an error message (or value) * following nil. In case of success, as long as the first value is not nil, * the other values don't matter. * * The idea is to nest function calls with the "try" function. This function * checks the first value, and, if it's falsy, wraps the second value in a * table with metatable and calls "error" on it. Otherwise, it returns all * values it received. Basically, it works like the Lua "assert" function, * but it creates errors targeted specifically at "protect". * * The "newtry" function is a factory for "try" functions that call a * finalizer in protected mode before calling "error". * * The "protect" function returns a new function that behaves exactly like * the function it receives, but the new function catches exceptions thrown * by "try" functions and returns nil followed by the error message instead. * * With these three functions, it's easy to write functions that throw * exceptions on error, but that don't interrupt the user script. \*=========================================================================*/ #include "luasocket.h" #ifndef _WIN32 #pragma GCC visibility push(hidden) #endif int except_open(lua_State *L); #ifndef _WIN32 #pragma GCC visibility pop #endif #endif ================================================ FILE: src/luasocket/ftp.lua ================================================ ----------------------------------------------------------------------------- -- FTP support for the Lua language -- LuaSocket toolkit. -- Author: Diego Nehab ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- -- Declare module and import dependencies ----------------------------------------------------------------------------- local tp = require("socket.tp") socket.ftp = {} local _M = socket.ftp ----------------------------------------------------------------------------- -- Program constants ----------------------------------------------------------------------------- -- timeout in seconds before the program gives up on a connection _M.TIMEOUT = 60 -- default port for ftp service local PORT = 21 -- this is the default anonymous password. used when no password is -- provided in url. should be changed to your e-mail. _M.USER = "ftp" _M.PASSWORD = "anonymous@anonymous.org" ----------------------------------------------------------------------------- -- Low level FTP API ----------------------------------------------------------------------------- local metat = { __index = {} } function _M.open(server, port, create) local tp = socket.try(tp.connect(server, port or PORT, _M.TIMEOUT, create)) local f = setmetatable({ tp = tp }, metat) -- make sure everything gets closed in an exception f.try = socket.newtry(function() f:close() end) return f end function metat.__index:portconnect() self.try(self.server:settimeout(_M.TIMEOUT)) self.data = self.try(self.server:accept()) self.try(self.data:settimeout(_M.TIMEOUT)) end function metat.__index:pasvconnect() self.data = self.try(socket.tcp()) self.try(self.data:settimeout(_M.TIMEOUT)) self.try(self.data:connect(self.pasvt.address, self.pasvt.port)) end function metat.__index:login(user, password) self.try(self.tp:command("user", user or _M.USER)) local code, reply = self.try(self.tp:check{"2..", 331}) if code == 331 then self.try(self.tp:command("pass", password or _M.PASSWORD)) self.try(self.tp:check("2..")) end return 1 end function metat.__index:pasv() self.try(self.tp:command("pasv")) local code, reply = self.try(self.tp:check("2..")) local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) self.try(a and b and c and d and p1 and p2, reply) self.pasvt = { address = string.format("%d.%d.%d.%d", a, b, c, d), port = p1*256 + p2 } if self.server then self.server:close() self.server = nil end return self.pasvt.address, self.pasvt.port end function metat.__index:epsv() self.try(self.tp:command("epsv")) local code, reply = self.try(self.tp:check("229")) local pattern = "%((.)(.-)%1(.-)%1(.-)%1%)" local d, prt, address, port = string.match(reply, pattern) self.try(port, "invalid epsv response") self.pasvt = { address = self.tp:getpeername(), port = port } if self.server then self.server:close() self.server = nil end return self.pasvt.address, self.pasvt.port end function metat.__index:port(address, port) self.pasvt = nil if not address then address, port = self.try(self.tp:getsockname()) self.server = self.try(socket.bind(address, 0)) address, port = self.try(self.server:getsockname()) self.try(self.server:settimeout(_M.TIMEOUT)) end local pl = math.mod(port, 256) local ph = (port - pl)/256 local arg = string.gsub(string.format("%s,%d,%d", address, ph, pl), "%.", ",") self.try(self.tp:command("port", arg)) self.try(self.tp:check("2..")) return 1 end function metat.__index:eprt(family, address, port) self.pasvt = nil if not address then address, port = self.try(self.tp:getsockname()) self.server = self.try(socket.bind(address, 0)) address, port = self.try(self.server:getsockname()) self.try(self.server:settimeout(_M.TIMEOUT)) end local arg = string.format("|%s|%s|%d|", family, address, port) self.try(self.tp:command("eprt", arg)) self.try(self.tp:check("2..")) return 1 end function metat.__index:send(sendt) self.try(self.pasvt or self.server, "need port or pasv first") -- if there is a pasvt table, we already sent a PASV command -- we just get the data connection into self.data if self.pasvt then self:pasvconnect() end -- get the transfer argument and command local argument = sendt.argument or url.unescape(string.gsub(sendt.path or "", "^[/\\]", "")) if argument == "" then argument = nil end local command = sendt.command or "stor" -- send the transfer command and check the reply self.try(self.tp:command(command, argument)) local code, reply = self.try(self.tp:check{"2..", "1.."}) -- if there is not a pasvt table, then there is a server -- and we already sent a PORT command if not self.pasvt then self:portconnect() end -- get the sink, source and step for the transfer local step = sendt.step or ltn12.pump.step local readt = { self.tp } local checkstep = function(src, snk) -- check status in control connection while downloading local readyt = socket.select(readt, nil, 0) if readyt[tp] then code = self.try(self.tp:check("2..")) end return step(src, snk) end local sink = socket.sink("close-when-done", self.data) -- transfer all data and check error self.try(ltn12.pump.all(sendt.source, sink, checkstep)) if string.find(code, "1..") then self.try(self.tp:check("2..")) end -- done with data connection self.data:close() -- find out how many bytes were sent local sent = socket.skip(1, self.data:getstats()) self.data = nil return sent end function metat.__index:receive(recvt) self.try(self.pasvt or self.server, "need port or pasv first") if self.pasvt then self:pasvconnect() end local argument = recvt.argument or url.unescape(string.gsub(recvt.path or "", "^[/\\]", "")) if argument == "" then argument = nil end local command = recvt.command or "retr" self.try(self.tp:command(command, argument)) local code,reply = self.try(self.tp:check{"1..", "2.."}) if (code >= 200) and (code <= 299) then recvt.sink(reply) return 1 end if not self.pasvt then self:portconnect() end local source = socket.source("until-closed", self.data) local step = recvt.step or ltn12.pump.step self.try(ltn12.pump.all(source, recvt.sink, step)) if string.find(code, "1..") then self.try(self.tp:check("2..")) end self.data:close() self.data = nil return 1 end function metat.__index:cwd(dir) self.try(self.tp:command("cwd", dir)) self.try(self.tp:check(250)) return 1 end function metat.__index:type(type) self.try(self.tp:command("type", type)) self.try(self.tp:check(200)) return 1 end function metat.__index:greet() local code = self.try(self.tp:check{"1..", "2.."}) if string.find(code, "1..") then self.try(self.tp:check("2..")) end return 1 end function metat.__index:quit() self.try(self.tp:command("quit")) self.try(self.tp:check("2..")) return 1 end function metat.__index:close() if self.data then self.data:close() end if self.server then self.server:close() end return self.tp:close() end ----------------------------------------------------------------------------- -- High level FTP API ----------------------------------------------------------------------------- local function override(t) if t.url then local u = url.parse(t.url) for i,v in pairs(t) do u[i] = v end return u else return t end end local function tput(putt) putt = override(putt) socket.try(putt.host, "missing hostname") local f = _M.open(putt.host, putt.port, putt.create) f:greet() f:login(putt.user, putt.password) if putt.type then f:type(putt.type) end f:epsv() local sent = f:send(putt) f:quit() f:close() return sent end local default = { path = "/", scheme = "ftp" } local function genericform(u) local t = socket.try(url.parse(u, default)) socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") socket.try(t.host, "missing hostname") local pat = "^type=(.)$" if t.params then t.type = socket.skip(2, string.find(t.params, pat)) socket.try(t.type == "a" or t.type == "i", "invalid type '" .. t.type .. "'") end return t end _M.genericform = genericform local function sput(u, body) local putt = genericform(u) putt.source = ltn12.source.string(body) return tput(putt) end _M.put = socket.protect(function(putt, body) if type(putt) == "string" then return sput(putt, body) else return tput(putt) end end) local function tget(gett) gett = override(gett) socket.try(gett.host, "missing hostname") local f = _M.open(gett.host, gett.port, gett.create) f:greet() f:login(gett.user, gett.password) if gett.type then f:type(gett.type) end f:epsv() f:receive(gett) f:quit() return f:close() end local function sget(u) local gett = genericform(u) local t = {} gett.sink = ltn12.sink.table(t) tget(gett) return table.concat(t) end _M.command = socket.protect(function(cmdt) cmdt = override(cmdt) socket.try(cmdt.host, "missing hostname") socket.try(cmdt.command, "missing command") local f = _M.open(cmdt.host, cmdt.port, cmdt.create) f:greet() f:login(cmdt.user, cmdt.password) if type(cmdt.command) == "table" then local argument = cmdt.argument or {} local check = cmdt.check or {} for i,cmd in ipairs(cmdt.command) do f.try(f.tp:command(cmd, argument[i])) if check[i] then f.try(f.tp:check(check[i])) end end else f.try(f.tp:command(cmdt.command, cmdt.argument)) if cmdt.check then f.try(f.tp:check(cmdt.check)) end end f:quit() return f:close() end) _M.get = socket.protect(function(gett) if type(gett) == "string" then return sget(gett) else return tget(gett) end end) return _M ================================================ FILE: src/luasocket/headers.lua ================================================ ----------------------------------------------------------------------------- -- Canonic header field capitalization -- LuaSocket toolkit. -- Author: Diego Nehab ----------------------------------------------------------------------------- socket.headers = {} local _M = socket.headers _M.canonic = { ["accept"] = "Accept", ["accept-charset"] = "Accept-Charset", ["accept-encoding"] = "Accept-Encoding", ["accept-language"] = "Accept-Language", ["accept-ranges"] = "Accept-Ranges", ["action"] = "Action", ["alternate-recipient"] = "Alternate-Recipient", ["age"] = "Age", ["allow"] = "Allow", ["arrival-date"] = "Arrival-Date", ["authorization"] = "Authorization", ["bcc"] = "Bcc", ["cache-control"] = "Cache-Control", ["cc"] = "Cc", ["comments"] = "Comments", ["connection"] = "Connection", ["content-description"] = "Content-Description", ["content-disposition"] = "Content-Disposition", ["content-encoding"] = "Content-Encoding", ["content-id"] = "Content-ID", ["content-language"] = "Content-Language", ["content-length"] = "Content-Length", ["content-location"] = "Content-Location", ["content-md5"] = "Content-MD5", ["content-range"] = "Content-Range", ["content-transfer-encoding"] = "Content-Transfer-Encoding", ["content-type"] = "Content-Type", ["cookie"] = "Cookie", ["date"] = "Date", ["diagnostic-code"] = "Diagnostic-Code", ["dsn-gateway"] = "DSN-Gateway", ["etag"] = "ETag", ["expect"] = "Expect", ["expires"] = "Expires", ["final-log-id"] = "Final-Log-ID", ["final-recipient"] = "Final-Recipient", ["from"] = "From", ["host"] = "Host", ["if-match"] = "If-Match", ["if-modified-since"] = "If-Modified-Since", ["if-none-match"] = "If-None-Match", ["if-range"] = "If-Range", ["if-unmodified-since"] = "If-Unmodified-Since", ["in-reply-to"] = "In-Reply-To", ["keywords"] = "Keywords", ["last-attempt-date"] = "Last-Attempt-Date", ["last-modified"] = "Last-Modified", ["location"] = "Location", ["max-forwards"] = "Max-Forwards", ["message-id"] = "Message-ID", ["mime-version"] = "MIME-Version", ["original-envelope-id"] = "Original-Envelope-ID", ["original-recipient"] = "Original-Recipient", ["pragma"] = "Pragma", ["proxy-authenticate"] = "Proxy-Authenticate", ["proxy-authorization"] = "Proxy-Authorization", ["range"] = "Range", ["received"] = "Received", ["received-from-mta"] = "Received-From-MTA", ["references"] = "References", ["referer"] = "Referer", ["remote-mta"] = "Remote-MTA", ["reply-to"] = "Reply-To", ["reporting-mta"] = "Reporting-MTA", ["resent-bcc"] = "Resent-Bcc", ["resent-cc"] = "Resent-Cc", ["resent-date"] = "Resent-Date", ["resent-from"] = "Resent-From", ["resent-message-id"] = "Resent-Message-ID", ["resent-reply-to"] = "Resent-Reply-To", ["resent-sender"] = "Resent-Sender", ["resent-to"] = "Resent-To", ["retry-after"] = "Retry-After", ["return-path"] = "Return-Path", ["sender"] = "Sender", ["server"] = "Server", ["smtp-remote-recipient"] = "SMTP-Remote-Recipient", ["status"] = "Status", ["subject"] = "Subject", ["te"] = "TE", ["to"] = "To", ["trailer"] = "Trailer", ["transfer-encoding"] = "Transfer-Encoding", ["upgrade"] = "Upgrade", ["user-agent"] = "User-Agent", ["vary"] = "Vary", ["via"] = "Via", ["warning"] = "Warning", ["will-retry-until"] = "Will-Retry-Until", ["www-authenticate"] = "WWW-Authenticate", ["x-mailer"] = "X-Mailer", } return _M ================================================ FILE: src/luasocket/http.lua ================================================ ----------------------------------------------------------------------------- -- HTTP/1.1 client support for the Lua language. -- LuaSocket toolkit. -- Author: Diego Nehab ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- -- Declare module and import dependencies ------------------------------------------------------------------------------- socket.http = {} local _M = socket.http ----------------------------------------------------------------------------- -- Program constants ----------------------------------------------------------------------------- -- connection timeout in seconds _M.TIMEOUT = 60 -- user agent field sent in request _M.USERAGENT = socket._VERSION -- supported schemes and their particulars local SCHEMES = { http = { port = 80 , create = function(t) return socket.tcp end } , https = { port = 443 , create = function(t) return https.tcp(t) end }} -- default scheme and port for document retrieval local SCHEME = 'http' local PORT = SCHEMES[SCHEME].port ----------------------------------------------------------------------------- -- Reads MIME headers from a connection, unfolding where needed ----------------------------------------------------------------------------- local function receiveheaders(sock, headers) local line, name, value, err headers = headers or {} -- get first line line, err = sock:receive() if err then return nil, err end -- headers go until a blank line is found while line ~= "" do -- get field-name and value name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) if not (name and value) then return nil, "malformed reponse headers" end name = string.lower(name) -- get next line (value might be folded) line, err = sock:receive() if err then return nil, err end -- unfold any folded values while string.find(line, "^%s") do value = value .. line line = sock:receive() if err then return nil, err end end -- save pair in table if headers[name] then headers[name] = headers[name] .. ", " .. value else headers[name] = value end end return headers end ----------------------------------------------------------------------------- -- Extra sources and sinks ----------------------------------------------------------------------------- socket.sourcet["http-chunked"] = function(sock, headers) return setmetatable({ getfd = function() return sock:getfd() end, dirty = function() return sock:dirty() end }, { __call = function() -- get chunk size, skip extention local line, err = sock:receive() if err then return nil, err end local size = tonumber(string.gsub(line, ";.*", ""), 16) if not size then return nil, "invalid chunk size" end -- was it the last chunk? if size > 0 then -- if not, get chunk and skip terminating CRLF local chunk, err, part = sock:receive(size) if chunk then sock:receive() end return chunk, err else -- if it was, read trailers into headers table headers, err = receiveheaders(sock, headers) if not headers then return nil, err end end end }) end socket.sinkt["http-chunked"] = function(sock) return setmetatable({ getfd = function() return sock:getfd() end, dirty = function() return sock:dirty() end }, { __call = function(self, chunk, err) if not chunk then return sock:send("0\r\n\r\n") end local size = string.format("%X\r\n", string.len(chunk)) return sock:send(size .. chunk .. "\r\n") end }) end ----------------------------------------------------------------------------- -- Low level HTTP API ----------------------------------------------------------------------------- local metat = { __index = {} } function _M.open(host, port, create) -- create socket with user connect function, or with default local c = socket.try(create()) local h = setmetatable({ c = c }, metat) -- create finalized try h.try = socket.newtry(function() h:close() end) -- set timeout before connecting h.try(c:settimeout(_M.TIMEOUT)) h.try(c:connect(host, port)) -- here everything worked return h end function metat.__index:sendrequestline(method, uri) local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) return self.try(self.c:send(reqline)) end function metat.__index:sendheaders(tosend) local canonic = headers.canonic local h = "\r\n" for f, v in pairs(tosend) do h = (canonic[f] or f) .. ": " .. v .. "\r\n" .. h end self.try(self.c:send(h)) return 1 end function metat.__index:sendbody(headers, source, step) source = source or ltn12.source.empty() step = step or ltn12.pump.step -- if we don't know the size in advance, send chunked and hope for the best local mode = "http-chunked" if headers["content-length"] then mode = "keep-open" end return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) end function metat.__index:receivestatusline() local status,ec = self.try(self.c:receive(5)) -- identify HTTP/0.9 responses, which do not contain a status line -- this is just a heuristic, but is what the RFC recommends if status ~= "HTTP/" then if ec == "timeout" then return 408 end return nil, status end -- otherwise proceed reading a status line status = self.try(self.c:receive("*l", status)) local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) return self.try(tonumber(code), status) end function metat.__index:receiveheaders() return self.try(receiveheaders(self.c)) end function metat.__index:receivebody(headers, sink, step) sink = sink or ltn12.sink.null() step = step or ltn12.pump.step local length = tonumber(headers["content-length"]) local t = headers["transfer-encoding"] -- shortcut local mode = "default" -- connection close if t and t ~= "identity" then mode = "http-chunked" elseif tonumber(headers["content-length"]) then mode = "by-length" end return self.try(ltn12.pump.all(socket.source(mode, self.c, length), sink, step)) end function metat.__index:receive09body(status, sink, step) local source = ltn12.source.rewind(socket.source("until-closed", self.c)) source(status) return self.try(ltn12.pump.all(source, sink, step)) end function metat.__index:close() return self.c:close() end ----------------------------------------------------------------------------- -- High level HTTP API ----------------------------------------------------------------------------- local function adjusturi(reqt) local u = reqt -- if there is a proxy, we need the full url. otherwise, just a part. if not reqt.proxy and not _M.PROXY then u = { path = socket.try(reqt.path, "invalid path 'nil'"), params = reqt.params, query = reqt.query, fragment = reqt.fragment } end return url.build(u) end local function adjustproxy(reqt) local proxy = reqt.proxy or _M.PROXY if proxy then proxy = url.parse(proxy) return proxy.host, proxy.port or 3128 else return reqt.host, reqt.port end end local function adjustheaders(reqt) -- default headers local host = reqt.host local port = tostring(reqt.port) if port ~= tostring(SCHEMES[reqt.scheme].port) then host = host .. ':' .. port end local lower = { ["user-agent"] = _M.USERAGENT, ["host"] = host, ["connection"] = "close, TE", ["te"] = "trailers" } -- if we have authentication information, pass it along if reqt.user and reqt.password then lower["authorization"] = "Basic " .. (mime.b64(reqt.user .. ":" .. url.unescape(reqt.password))) end -- if we have proxy authentication information, pass it along local proxy = reqt.proxy or _M.PROXY if proxy then proxy = url.parse(proxy) if proxy.user and proxy.password then lower["proxy-authorization"] = "Basic " .. (mime.b64(proxy.user .. ":" .. proxy.password)) end end -- override with user headers for i,v in pairs(reqt.headers or lower) do lower[string.lower(i)] = v end return lower end -- default url parts local default = { path ="/" , scheme = "http" } local function adjustrequest(reqt) -- parse url if provided local nreqt = reqt.url and url.parse(reqt.url, default) or {} -- explicit components override url for i,v in pairs(reqt) do nreqt[i] = v end -- default to scheme particulars local schemedefs, host, port, method = SCHEMES[nreqt.scheme], nreqt.host, nreqt.port, nreqt.method if not nreqt.create then nreqt.create = schemedefs.create(nreqt) end if not (port and port ~= '') then nreqt.port = schemedefs.port end if not (method and method ~= '') then nreqt.method = 'GET' end if not (host and host ~= "") then socket.try(nil, "invalid host '" .. tostring(nreqt.host) .. "'") end -- compute uri if user hasn't overriden nreqt.uri = reqt.uri or adjusturi(nreqt) -- adjust headers in request nreqt.headers = adjustheaders(nreqt) -- ajust host and port if there is a proxy nreqt.host, nreqt.port = adjustproxy(nreqt) return nreqt end local function shouldredirect(reqt, code, headers) local location = headers.location if not location then return false end location = string.gsub(location, "%s", "") if location == "" then return false end local scheme = url.parse(location).scheme if scheme and (not SCHEMES[scheme]) then return false end -- avoid https downgrades if ('https' == reqt.scheme) and ('https' ~= scheme) then return false end return (reqt.redirect ~= false) and (code == 301 or code == 302 or code == 303 or code == 307) and (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") and ((false == reqt.maxredirects) or ((reqt.nredirects or 0) < (reqt.maxredirects or 5))) end local function shouldreceivebody(reqt, code) if reqt.method == "HEAD" then return nil end if code == 204 or code == 304 then return nil end if code >= 100 and code < 200 then return nil end return 1 end -- forward declarations local trequest, tredirect --[[local]] function tredirect(reqt, location) -- the RFC says the redirect URL has to be absolute, but some -- servers do not respect that local newurl = url.absolute(reqt.url, location) -- if switching schemes, reset port and create function if url.parse(newurl).scheme ~= reqt.scheme then reqt.port = nil reqt.create = nil end -- make new request local result, code, headers, status = trequest { url = newurl, source = reqt.source, sink = reqt.sink, headers = reqt.headers, proxy = reqt.proxy, maxredirects = reqt.maxredirects, nredirects = (reqt.nredirects or 0) + 1, create = reqt.create } -- pass location header back as a hint we redirected headers = headers or {} headers.location = headers.location or location return result, code, headers, status end --[[local]] function trequest(reqt) -- we loop until we get what we want, or -- until we are sure there is no way to get it local nreqt = adjustrequest(reqt) local h = _M.open(nreqt.host, nreqt.port, nreqt.create) -- send request line and headers h:sendrequestline(nreqt.method, nreqt.uri) h:sendheaders(nreqt.headers) -- if there is a body, send it if nreqt.source then h:sendbody(nreqt.headers, nreqt.source, nreqt.step) end local code, status = h:receivestatusline() -- if it is an HTTP/0.9 server, simply get the body and we are done if not code then h:receive09body(status, nreqt.sink, nreqt.step) return 1, 200 elseif code == 408 then return 1, code end local headers -- ignore any 100-continue messages while code == 100 do headers = h:receiveheaders() code, status = h:receivestatusline() end headers = h:receiveheaders() -- at this point we should have a honest reply from the server -- we can't redirect if we already used the source, so we report the error if shouldredirect(nreqt, code, headers) and not nreqt.source then h:close() return tredirect(reqt, headers.location) end -- here we are finally done if shouldreceivebody(nreqt, code) then h:receivebody(headers, nreqt.sink, nreqt.step) end h:close() return 1, code, headers, status end -- turns an url and a body into a generic request local function genericform(u, b) local t = {} local reqt = { url = u, sink = ltn12.sink.table(t), target = t } if b then reqt.source = ltn12.source.string(b) reqt.headers = { ["content-length"] = string.len(b), ["content-type"] = "application/x-www-form-urlencoded" } reqt.method = "POST" end return reqt end _M.genericform = genericform local function srequest(u, b) local reqt = genericform(u, b) local _, code, headers, status = trequest(reqt) return table.concat(reqt.target), code, headers, status end _M.request = socket.protect(function(reqt, body) if type(reqt) == "string" then return srequest(reqt, body) else return trequest(reqt) end end) _M.schemes = SCHEMES return _M ================================================ FILE: src/luasocket/inet.c ================================================ /*=========================================================================*\ * Internet domain functions * LuaSocket toolkit \*=========================================================================*/ #include "luasocket.h" #include "inet.h" #include "../teliva.h" #include #include #include /*=========================================================================*\ * Internal function prototypes. \*=========================================================================*/ static int inet_global_toip(lua_State *L); static int inet_global_getaddrinfo(lua_State *L); static int inet_global_tohostname(lua_State *L); static int inet_global_getnameinfo(lua_State *L); static void inet_pushresolved(lua_State *L, struct hostent *hp); static int inet_global_gethostname(lua_State *L); /* DNS functions */ static luaL_Reg func[] = { { "toip", inet_global_toip}, { "getaddrinfo", inet_global_getaddrinfo}, { "tohostname", inet_global_tohostname}, { "getnameinfo", inet_global_getnameinfo}, { "gethostname", inet_global_gethostname}, { NULL, NULL} }; /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ int inet_open(lua_State *L) { lua_pushstring(L, "dns"); lua_newtable(L); luaL_setfuncs(L, func, 0); lua_settable(L, -3); return 0; } /*=========================================================================*\ * Global Lua functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Returns all information provided by the resolver given a host name * or ip address \*-------------------------------------------------------------------------*/ static int inet_gethost(const char *address, struct hostent **hp) { struct in_addr addr; if (inet_aton(address, &addr)) return socket_gethostbyaddr((char *) &addr, sizeof(addr), hp); else return socket_gethostbyname(address, hp); } /*-------------------------------------------------------------------------*\ * Returns all information provided by the resolver given a host name * or ip address \*-------------------------------------------------------------------------*/ static int inet_global_tohostname(lua_State *L) { const char *address = luaL_checkstring(L, 1); static char buffer[1024] = {0}; memset(buffer, '\0', 1024); snprintf(buffer, 1020, "socket.tohostname(\"%s\")", address); append_to_audit_log(L, buffer); struct hostent *hp = NULL; int err = inet_gethost(address, &hp); if (err != IO_DONE) { lua_pushnil(L); lua_pushstring(L, socket_hoststrerror(err)); return 2; } lua_pushstring(L, hp->h_name); inet_pushresolved(L, hp); return 2; } static int inet_global_getnameinfo(lua_State *L) { char hbuf[NI_MAXHOST]; char sbuf[NI_MAXSERV]; int i, ret; struct addrinfo hints; struct addrinfo *resolved, *iter; const char *host = luaL_optstring(L, 1, NULL); const char *serv = luaL_optstring(L, 2, NULL); static char buffer[1024] = {0}; memset(buffer, '\0', 1024); snprintf(buffer, 1020, "socket.getnameinfo(\"%s\", \"%s\")", host, serv); append_to_audit_log(L, buffer); if (!(host || serv)) luaL_error(L, "host and serv cannot be both nil"); memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC; ret = getaddrinfo(host, serv, &hints, &resolved); if (ret != 0) { lua_pushnil(L); lua_pushstring(L, socket_gaistrerror(ret)); return 2; } lua_newtable(L); for (i = 1, iter = resolved; iter; i++, iter = iter->ai_next) { getnameinfo(iter->ai_addr, (socklen_t) iter->ai_addrlen, hbuf, host? (socklen_t) sizeof(hbuf): 0, sbuf, serv? (socklen_t) sizeof(sbuf): 0, 0); if (host) { lua_pushnumber(L, i); lua_pushstring(L, hbuf); lua_settable(L, -3); } } freeaddrinfo(resolved); if (serv) { lua_pushstring(L, sbuf); return 2; } else { return 1; } } /*-------------------------------------------------------------------------*\ * Returns all information provided by the resolver given a host name * or ip address \*-------------------------------------------------------------------------*/ static int inet_global_toip(lua_State *L) { const char *address = luaL_checkstring(L, 1); struct hostent *hp = NULL; static char buffer[1024] = {0}; memset(buffer, '\0', 1024); snprintf(buffer, 1020, "socket.toip(\"%s\")", address); append_to_audit_log(L, buffer); int err = inet_gethost(address, &hp); if (err != IO_DONE) { lua_pushnil(L); lua_pushstring(L, socket_hoststrerror(err)); return 2; } lua_pushstring(L, inet_ntoa(*((struct in_addr *) hp->h_addr))); inet_pushresolved(L, hp); return 2; } int inet_optfamily(lua_State* L, int narg, const char* def) { static const char* optname[] = { "unspec", "inet", "inet6", NULL }; static int optvalue[] = { AF_UNSPEC, AF_INET, AF_INET6, 0 }; return optvalue[luaL_checkoption(L, narg, def, optname)]; } int inet_optsocktype(lua_State* L, int narg, const char* def) { static const char* optname[] = { "stream", "dgram", NULL }; static int optvalue[] = { SOCK_STREAM, SOCK_DGRAM, 0 }; return optvalue[luaL_checkoption(L, narg, def, optname)]; } static int inet_global_getaddrinfo(lua_State *L) { const char *hostname = luaL_checkstring(L, 1); struct addrinfo *iterator = NULL, *resolved = NULL; struct addrinfo hints; int i = 1, ret = 0; memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC; ret = getaddrinfo(hostname, NULL, &hints, &resolved); if (ret != 0) { lua_pushnil(L); lua_pushstring(L, socket_gaistrerror(ret)); return 2; } lua_newtable(L); for (iterator = resolved; iterator; iterator = iterator->ai_next) { char hbuf[NI_MAXHOST]; ret = getnameinfo(iterator->ai_addr, (socklen_t) iterator->ai_addrlen, hbuf, (socklen_t) sizeof(hbuf), NULL, 0, NI_NUMERICHOST); if (ret){ freeaddrinfo(resolved); lua_pushnil(L); lua_pushstring(L, socket_gaistrerror(ret)); return 2; } lua_pushnumber(L, i); lua_newtable(L); switch (iterator->ai_family) { case AF_INET: lua_pushliteral(L, "family"); lua_pushliteral(L, "inet"); lua_settable(L, -3); break; case AF_INET6: lua_pushliteral(L, "family"); lua_pushliteral(L, "inet6"); lua_settable(L, -3); break; case AF_UNSPEC: lua_pushliteral(L, "family"); lua_pushliteral(L, "unspec"); lua_settable(L, -3); break; default: lua_pushliteral(L, "family"); lua_pushliteral(L, "unknown"); lua_settable(L, -3); break; } lua_pushliteral(L, "addr"); lua_pushstring(L, hbuf); lua_settable(L, -3); lua_settable(L, -3); i++; } freeaddrinfo(resolved); return 1; } /*-------------------------------------------------------------------------*\ * Gets the host name \*-------------------------------------------------------------------------*/ static int inet_global_gethostname(lua_State *L) { char name[257]; name[256] = '\0'; if (gethostname(name, 256) < 0) { lua_pushnil(L); lua_pushstring(L, socket_strerror(errno)); return 2; } else { lua_pushstring(L, name); return 1; } } /*=========================================================================*\ * Lua methods \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Retrieves socket peer name \*-------------------------------------------------------------------------*/ int inet_meth_getpeername(lua_State *L, p_socket ps, int family) { int err; struct sockaddr_storage peer; socklen_t peer_len = sizeof(peer); char name[INET6_ADDRSTRLEN]; char port[6]; /* 65535 = 5 bytes + 0 to terminate it */ if (getpeername(*ps, (SA *) &peer, &peer_len) < 0) { lua_pushnil(L); lua_pushstring(L, socket_strerror(errno)); return 2; } err = getnameinfo((struct sockaddr *) &peer, peer_len, name, INET6_ADDRSTRLEN, port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); if (err) { lua_pushnil(L); lua_pushstring(L, gai_strerror(err)); return 2; } lua_pushstring(L, name); lua_pushinteger(L, (int) strtol(port, (char **) NULL, 10)); switch (family) { case AF_INET: lua_pushliteral(L, "inet"); break; case AF_INET6: lua_pushliteral(L, "inet6"); break; case AF_UNSPEC: lua_pushliteral(L, "unspec"); break; default: lua_pushliteral(L, "unknown"); break; } return 3; } /*-------------------------------------------------------------------------*\ * Retrieves socket local name \*-------------------------------------------------------------------------*/ int inet_meth_getsockname(lua_State *L, p_socket ps, int family) { int err; struct sockaddr_storage peer; socklen_t peer_len = sizeof(peer); char name[INET6_ADDRSTRLEN]; char port[6]; /* 65535 = 5 bytes + 0 to terminate it */ if (getsockname(*ps, (SA *) &peer, &peer_len) < 0) { lua_pushnil(L); lua_pushstring(L, socket_strerror(errno)); return 2; } err=getnameinfo((struct sockaddr *)&peer, peer_len, name, INET6_ADDRSTRLEN, port, 6, NI_NUMERICHOST | NI_NUMERICSERV); if (err) { lua_pushnil(L); lua_pushstring(L, gai_strerror(err)); return 2; } lua_pushstring(L, name); lua_pushstring(L, port); switch (family) { case AF_INET: lua_pushliteral(L, "inet"); break; case AF_INET6: lua_pushliteral(L, "inet6"); break; case AF_UNSPEC: lua_pushliteral(L, "unspec"); break; default: lua_pushliteral(L, "unknown"); break; } return 3; } /*=========================================================================*\ * Internal functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Passes all resolver information to Lua as a table \*-------------------------------------------------------------------------*/ static void inet_pushresolved(lua_State *L, struct hostent *hp) { char **alias; struct in_addr **addr; int i, resolved; lua_newtable(L); resolved = lua_gettop(L); lua_pushstring(L, "name"); lua_pushstring(L, hp->h_name); lua_settable(L, resolved); lua_pushstring(L, "ip"); lua_pushstring(L, "alias"); i = 1; alias = hp->h_aliases; lua_newtable(L); if (alias) { while (*alias) { lua_pushnumber(L, i); lua_pushstring(L, *alias); lua_settable(L, -3); i++; alias++; } } lua_settable(L, resolved); i = 1; lua_newtable(L); addr = (struct in_addr **) hp->h_addr_list; if (addr) { while (*addr) { lua_pushnumber(L, i); lua_pushstring(L, inet_ntoa(**addr)); lua_settable(L, -3); i++; addr++; } } lua_settable(L, resolved); } /*-------------------------------------------------------------------------*\ * Tries to create a new inet socket \*-------------------------------------------------------------------------*/ const char *inet_trycreate(p_socket ps, int family, int type, int protocol) { const char *err = socket_strerror(socket_create(ps, family, type, protocol)); if (err == NULL && family == AF_INET6) { int yes = 1; setsockopt(*ps, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&yes, sizeof(yes)); } return err; } /*-------------------------------------------------------------------------*\ * "Disconnects" a DGRAM socket \*-------------------------------------------------------------------------*/ const char *inet_trydisconnect(p_socket ps, int family, p_timeout tm) { switch (family) { case AF_INET: { struct sockaddr_in sin; memset((char *) &sin, 0, sizeof(sin)); sin.sin_family = AF_UNSPEC; sin.sin_addr.s_addr = INADDR_ANY; return socket_strerror(socket_connect(ps, (SA *) &sin, sizeof(sin), tm)); } case AF_INET6: { struct sockaddr_in6 sin6; struct in6_addr addrany = IN6ADDR_ANY_INIT; memset((char *) &sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_UNSPEC; sin6.sin6_addr = addrany; return socket_strerror(socket_connect(ps, (SA *) &sin6, sizeof(sin6), tm)); } } return NULL; } /*-------------------------------------------------------------------------*\ * Tries to connect to remote address (address, port) \*-------------------------------------------------------------------------*/ const char *inet_tryconnect(p_socket ps, int *family, const char *address, const char *serv, p_timeout tm, struct addrinfo *connecthints) { struct addrinfo *iterator = NULL, *resolved = NULL; const char *err = NULL; int current_family = *family; /* try resolving */ err = socket_gaistrerror(getaddrinfo(address, serv, connecthints, &resolved)); if (err != NULL) { if (resolved) freeaddrinfo(resolved); return err; } for (iterator = resolved; iterator; iterator = iterator->ai_next) { timeout_markstart(tm); /* create new socket if necessary. if there was no * bind, we need to create one for every new family * that shows up while iterating. if there was a * bind, all families will be the same and we will * not enter this branch. */ if (current_family != iterator->ai_family || *ps == SOCKET_INVALID) { socket_destroy(ps); err = inet_trycreate(ps, iterator->ai_family, iterator->ai_socktype, iterator->ai_protocol); if (err) continue; current_family = iterator->ai_family; /* set non-blocking before connect */ socket_setnonblocking(ps); } /* try connecting to remote address */ err = socket_strerror(socket_connect(ps, (SA *) iterator->ai_addr, (socklen_t) iterator->ai_addrlen, tm)); /* if success or timeout is zero, break out of loop */ if (err == NULL || timeout_iszero(tm)) { *family = current_family; break; } } freeaddrinfo(resolved); /* here, if err is set, we failed */ return err; } /*-------------------------------------------------------------------------*\ * Tries to accept a socket \*-------------------------------------------------------------------------*/ const char *inet_tryaccept(p_socket server, int family, p_socket client, p_timeout tm) { socklen_t len; t_sockaddr_storage addr; switch (family) { case AF_INET6: len = sizeof(struct sockaddr_in6); break; case AF_INET: len = sizeof(struct sockaddr_in); break; default: len = sizeof(addr); break; } return socket_strerror(socket_accept(server, client, (SA *) &addr, &len, tm)); } /*-------------------------------------------------------------------------*\ * Tries to bind socket to (address, port) \*-------------------------------------------------------------------------*/ const char *inet_trybind(p_socket ps, int *family, const char *address, const char *serv, struct addrinfo *bindhints) { struct addrinfo *iterator = NULL, *resolved = NULL; const char *err = NULL; int current_family = *family; /* translate luasocket special values to C */ if (strcmp(address, "*") == 0) address = NULL; if (!serv) serv = "0"; /* try resolving */ err = socket_gaistrerror(getaddrinfo(address, serv, bindhints, &resolved)); if (err) { if (resolved) freeaddrinfo(resolved); return err; } /* iterate over resolved addresses until one is good */ for (iterator = resolved; iterator; iterator = iterator->ai_next) { if (current_family != iterator->ai_family || *ps == SOCKET_INVALID) { socket_destroy(ps); err = inet_trycreate(ps, iterator->ai_family, iterator->ai_socktype, iterator->ai_protocol); if (err) continue; current_family = iterator->ai_family; } /* try binding to local address */ err = socket_strerror(socket_bind(ps, (SA *) iterator->ai_addr, (socklen_t) iterator->ai_addrlen)); /* keep trying unless bind succeeded */ if (err == NULL) { *family = current_family; /* set to non-blocking after bind */ socket_setnonblocking(ps); break; } } /* cleanup and return error */ freeaddrinfo(resolved); /* here, if err is set, we failed */ return err; } /*-------------------------------------------------------------------------*\ * Some systems do not provide these so that we provide our own. \*-------------------------------------------------------------------------*/ #ifdef LUASOCKET_INET_ATON int inet_aton(const char *cp, struct in_addr *inp) { unsigned int a = 0, b = 0, c = 0, d = 0; int n = 0, r; unsigned long int addr = 0; r = sscanf(cp, "%u.%u.%u.%u%n", &a, &b, &c, &d, &n); if (r == 0 || n == 0) return 0; cp += n; if (*cp) return 0; if (a > 255 || b > 255 || c > 255 || d > 255) return 0; if (inp) { addr += a; addr <<= 8; addr += b; addr <<= 8; addr += c; addr <<= 8; addr += d; inp->s_addr = htonl(addr); } return 1; } #endif #ifdef LUASOCKET_INET_PTON int inet_pton(int af, const char *src, void *dst) { struct addrinfo hints, *res; int ret = 1; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = af; hints.ai_flags = AI_NUMERICHOST; if (getaddrinfo(src, NULL, &hints, &res) != 0) return -1; if (af == AF_INET) { struct sockaddr_in *in = (struct sockaddr_in *) res->ai_addr; memcpy(dst, &in->sin_addr, sizeof(in->sin_addr)); } else if (af == AF_INET6) { struct sockaddr_in6 *in = (struct sockaddr_in6 *) res->ai_addr; memcpy(dst, &in->sin6_addr, sizeof(in->sin6_addr)); } else { ret = -1; } freeaddrinfo(res); return ret; } #endif ================================================ FILE: src/luasocket/inet.h ================================================ #ifndef INET_H #define INET_H /*=========================================================================*\ * Internet domain functions * LuaSocket toolkit * * This module implements the creation and connection of internet domain * sockets, on top of the socket.h interface, and the interface of with the * resolver. * * The function inet_aton is provided for the platforms where it is not * available. The module also implements the interface of the internet * getpeername and getsockname functions as seen by Lua programs. * * The Lua functions toip and tohostname are also implemented here. \*=========================================================================*/ #include "luasocket.h" #include "socket.h" #include "timeout.h" #ifdef _WIN32 #define LUASOCKET_INET_ATON #endif #ifndef _WIN32 #pragma GCC visibility push(hidden) #endif int inet_open(lua_State *L); int inet_optfamily(lua_State* L, int narg, const char* def); int inet_optsocktype(lua_State* L, int narg, const char* def); int inet_meth_getpeername(lua_State *L, p_socket ps, int family); int inet_meth_getsockname(lua_State *L, p_socket ps, int family); const char *inet_trycreate(p_socket ps, int family, int type, int protocol); const char *inet_trydisconnect(p_socket ps, int family, p_timeout tm); const char *inet_tryconnect(p_socket ps, int *family, const char *address, const char *serv, p_timeout tm, struct addrinfo *connecthints); const char *inet_tryaccept(p_socket server, int family, p_socket client, p_timeout tm); const char *inet_trybind(p_socket ps, int *family, const char *address, const char *serv, struct addrinfo *bindhints); #ifdef LUASOCKET_INET_ATON int inet_aton(const char *cp, struct in_addr *inp); #endif #ifdef LUASOCKET_INET_PTON const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt); int inet_pton(int af, const char *src, void *dst); #endif #ifndef _WIN32 #pragma GCC visibility pop #endif #endif /* INET_H */ ================================================ FILE: src/luasocket/io.c ================================================ /*=========================================================================*\ * Input/Output abstraction * LuaSocket toolkit \*=========================================================================*/ #include "luasocket.h" #include "io.h" /*-------------------------------------------------------------------------*\ * Initializes C structure \*-------------------------------------------------------------------------*/ void io_init(p_io io, p_send send, p_recv recv, p_error error, void *ctx) { io->send = send; io->recv = recv; io->error = error; io->ctx = ctx; } /*-------------------------------------------------------------------------*\ * I/O error strings \*-------------------------------------------------------------------------*/ const char *io_strerror(int err) { switch (err) { case IO_DONE: return NULL; case IO_CLOSED: return "closed"; case IO_TIMEOUT: return "timeout"; default: return "unknown error"; } } ================================================ FILE: src/luasocket/io.h ================================================ #ifndef IO_H #define IO_H /*=========================================================================*\ * Input/Output abstraction * LuaSocket toolkit * * This module defines the interface that LuaSocket expects from the * transport layer for streamed input/output. The idea is that if any * transport implements this interface, then the buffer.c functions * automatically work on it. * * The module socket.h implements this interface, and thus the module tcp.h * is very simple. \*=========================================================================*/ #include "luasocket.h" #include "timeout.h" /* IO error codes */ enum { IO_DONE = 0, /* operation completed successfully */ IO_TIMEOUT = -1, /* operation timed out */ IO_CLOSED = -2, /* the connection has been closed */ IO_UNKNOWN = -3 }; /* interface to error message function */ typedef const char *(*p_error) ( void *ctx, /* context needed by send */ int err /* error code */ ); /* interface to send function */ typedef int (*p_send) ( void *ctx, /* context needed by send */ const char *data, /* pointer to buffer with data to send */ size_t count, /* number of bytes to send from buffer */ size_t *sent, /* number of bytes sent uppon return */ p_timeout tm /* timeout control */ ); /* interface to recv function */ typedef int (*p_recv) ( void *ctx, /* context needed by recv */ char *data, /* pointer to buffer where data will be writen */ size_t count, /* number of bytes to receive into buffer */ size_t *got, /* number of bytes received uppon return */ p_timeout tm /* timeout control */ ); /* IO driver definition */ typedef struct t_io_ { void *ctx; /* context needed by send/recv */ p_send send; /* send function pointer */ p_recv recv; /* receive function pointer */ p_error error; /* strerror function */ } t_io; typedef t_io *p_io; #ifndef _WIN32 #pragma GCC visibility push(hidden) #endif void io_init(p_io io, p_send send, p_recv recv, p_error error, void *ctx); const char *io_strerror(int err); #ifndef _WIN32 #pragma GCC visibility pop #endif #endif /* IO_H */ ================================================ FILE: src/luasocket/ltn12.lua ================================================ ----------------------------------------------------------------------------- -- LTN12 - Filters, sources, sinks and pumps. -- LuaSocket toolkit. -- Author: Diego Nehab ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- -- Declare module ----------------------------------------------------------------------------- local _M = {} local filter,source,sink,pump = {},{},{},{} _M.filter = filter _M.source = source _M.sink = sink _M.pump = pump -- 2048 seems to be better in windows... _M.BLOCKSIZE = 2048 _M._VERSION = "LTN12 1.0.3" ----------------------------------------------------------------------------- -- Filter stuff ----------------------------------------------------------------------------- -- returns a high level filter that cycles a low-level filter function filter.cycle(low, ctx, extra) assert(low) return function(chunk) local ret ret, ctx = low(ctx, chunk, extra) return ret end end -- chains a bunch of filters together -- (thanks to Wim Couwenberg) function filter.chain(...) local arg = {...} local n = select('#',...) local top, index = 1, 1 local retry = "" return function(chunk) retry = chunk and retry while true do if index == top then chunk = arg[index](chunk) if chunk == "" or top == n then return chunk elseif chunk then index = index + 1 else top = top+1 index = top end else chunk = arg[index](chunk or "") if chunk == "" then index = index - 1 chunk = retry elseif chunk then if index == n then return chunk else index = index + 1 end else error("filter returned inappropriate nil") end end end end end ----------------------------------------------------------------------------- -- Source stuff ----------------------------------------------------------------------------- -- create an empty source local function empty() return nil end function source.empty() return empty end -- returns a source that just outputs an error function source.error(err) return function() return nil, err end end -- creates a file source function source.file(handle, io_err) if handle then return function() local chunk = handle:read(_M.BLOCKSIZE) if not chunk then handle:close() end return chunk end else return source.error(io_err or "unable to open file") end end -- turns a fancy source into a simple source function source.simplify(src) assert(src) return function() local chunk, err_or_new = src() src = err_or_new or src if not chunk then return nil, err_or_new else return chunk end end end -- creates string source function source.string(s) if s then local i = 1 return function() local chunk = string.sub(s, i, i+_M.BLOCKSIZE-1) i = i + _M.BLOCKSIZE if chunk ~= "" then return chunk else return nil end end else return source.empty() end end -- creates table source function source.table(t) assert('table' == type(t)) local i = 0 return function() i = i + 1 return t[i] end end -- creates rewindable source function source.rewind(src) assert(src) local t = {} return function(chunk) if not chunk then chunk = table.remove(t) if not chunk then return src() else return chunk end else table.insert(t, chunk) end end end -- chains a source with one or several filter(s) function source.chain(src, f, ...) if ... then f=filter.chain(f, ...) end assert(src and f) local last_in, last_out = "", "" local state = "feeding" local err return function() if not last_out then error('source is empty!', 2) end while true do if state == "feeding" then last_in, err = src() if err then return nil, err end last_out = f(last_in) if not last_out then if last_in then error('filter returned inappropriate nil') else return nil end elseif last_out ~= "" then state = "eating" if last_in then last_in = "" end return last_out end else last_out = f(last_in) if last_out == "" then if last_in == "" then state = "feeding" else error('filter returned ""') end elseif not last_out then if last_in then error('filter returned inappropriate nil') else return nil end else return last_out end end end end end -- creates a source that produces contents of several sources, one after the -- other, as if they were concatenated -- (thanks to Wim Couwenberg) function source.cat(...) local arg = {...} local src = table.remove(arg, 1) return function() while src do local chunk, err = src() if chunk then return chunk end if err then return nil, err end src = table.remove(arg, 1) end end end ----------------------------------------------------------------------------- -- Sink stuff ----------------------------------------------------------------------------- -- creates a sink that stores into a table function sink.table(t) t = t or {} local f = function(chunk, err) if chunk then table.insert(t, chunk) end return 1 end return f, t end -- turns a fancy sink into a simple sink function sink.simplify(snk) assert(snk) return function(chunk, err) local ret, err_or_new = snk(chunk, err) if not ret then return nil, err_or_new end snk = err_or_new or snk return 1 end end -- creates a file sink function sink.file(handle, io_err) if handle then return function(chunk, err) if not chunk then handle:close() return 1 else return handle:write(chunk) end end else return sink.error(io_err or "unable to open file") end end -- creates a sink that discards data local function null() return 1 end function sink.null() return null end -- creates a sink that just returns an error function sink.error(err) return function() return nil, err end end -- chains a sink with one or several filter(s) function sink.chain(f, snk, ...) if ... then local args = { f, snk, ... } snk = table.remove(args, #args) f = filter.chain(unpack(args)) end assert(f and snk) return function(chunk, err) if chunk ~= "" then local filtered = f(chunk) local done = chunk and "" while true do local ret, snkerr = snk(filtered, err) if not ret then return nil, snkerr end if filtered == done then return 1 end filtered = f(done) end else return 1 end end end ----------------------------------------------------------------------------- -- Pump stuff ----------------------------------------------------------------------------- -- pumps one chunk from the source to the sink function pump.step(src, snk) local chunk, src_err = src() local ret, snk_err = snk(chunk, src_err) if chunk and ret then return 1 else return nil, src_err or snk_err end end -- pumps all data from a source to a sink, using a step function function pump.all(src, snk, step) assert(src and snk) step = step or pump.step while true do local ret, err = step(src, snk) if not ret then if err then return nil, err else return 1 end end end end return _M ================================================ FILE: src/luasocket/luasocket.c ================================================ /*=========================================================================*\ * LuaSocket toolkit * Networking support for the Lua language * Diego Nehab * 26/11/1999 * * This library is part of an effort to progressively increase the network * connectivity of the Lua language. The Lua interface to networking * functions follows the Sockets API closely, trying to simplify all tasks * involved in setting up both client and server connections. The provided * IO routines, however, follow the Lua style, being very similar to the * standard Lua read and write functions. \*=========================================================================*/ #include "luasocket.h" #include "auxiliar.h" #include "except.h" #include "timeout.h" #include "buffer.h" #include "inet.h" #include "tcp.h" #include "udp.h" #include "select.h" /*-------------------------------------------------------------------------*\ * Internal function prototypes \*-------------------------------------------------------------------------*/ static int global_skip(lua_State *L); static int global_unload(lua_State *L); static int base_open(lua_State *L); /*-------------------------------------------------------------------------*\ * Modules and functions \*-------------------------------------------------------------------------*/ static const luaL_Reg mod[] = { {"auxiliar", auxiliar_open}, {"except", except_open}, {"timeout", timeout_open}, {"buffer", buffer_open}, {"inet", inet_open}, {"tcp", tcp_open}, {"udp", udp_open}, {"select", select_open}, {NULL, NULL} }; static luaL_Reg func[] = { {"skip", global_skip}, {"__unload", global_unload}, {NULL, NULL} }; /*-------------------------------------------------------------------------*\ * Skip a few arguments \*-------------------------------------------------------------------------*/ static int global_skip(lua_State *L) { int amount = (int) luaL_checkinteger(L, 1); int ret = lua_gettop(L) - amount - 1; return ret >= 0 ? ret : 0; } /*-------------------------------------------------------------------------*\ * Unloads the library \*-------------------------------------------------------------------------*/ static int global_unload(lua_State *L) { (void) L; socket_close(); return 0; } /*-------------------------------------------------------------------------*\ * Setup basic stuff. \*-------------------------------------------------------------------------*/ static int base_open(lua_State *L) { if (socket_open()) { /* export functions (and leave namespace table on top of stack) */ lua_newtable(L); luaL_setfuncs(L, func, 0); #ifdef LUASOCKET_DEBUG lua_pushstring(L, "_DEBUG"); lua_pushboolean(L, 1); lua_rawset(L, -3); #endif /* make version string available to scripts */ lua_pushstring(L, "_VERSION"); lua_pushstring(L, LUASOCKET_VERSION); lua_rawset(L, -3); return 1; } else { lua_pushstring(L, "unable to initialize library"); lua_error(L); return 0; } } /*-------------------------------------------------------------------------*\ * Initializes all library modules. \*-------------------------------------------------------------------------*/ LUASOCKET_API int luaopen_socket_core(lua_State *L) { int i; base_open(L); for (i = 0; mod[i].name; i++) mod[i].func(L); lua_setglobal(L, "socket"); return 1; } ================================================ FILE: src/luasocket/luasocket.h ================================================ #ifndef LUASOCKET_H #define LUASOCKET_H /*=========================================================================*\ * LuaSocket toolkit * Networking support for the Lua language * Diego Nehab * 9/11/1999 \*=========================================================================*/ /*-------------------------------------------------------------------------* \ * Current socket library version \*-------------------------------------------------------------------------*/ #define LUASOCKET_VERSION "LuaSocket 3.0-rc1" #define LUASOCKET_COPYRIGHT "Copyright (C) 1999-2013 Diego Nehab" /*-------------------------------------------------------------------------*\ * This macro prefixes all exported API functions \*-------------------------------------------------------------------------*/ #ifndef LUASOCKET_API #ifdef _WIN32 #define LUASOCKET_API __declspec(dllexport) #else #define LUASOCKET_API __attribute__ ((visibility ("default"))) #endif #endif #include "../lua.h" #include "../lauxlib.h" #include "compat.h" /*-------------------------------------------------------------------------*\ * Initializes the library. \*-------------------------------------------------------------------------*/ LUASOCKET_API int luaopen_socket_core(lua_State *L); #endif /* LUASOCKET_H */ ================================================ FILE: src/luasocket/makefile ================================================ # luasocket src/makefile # # Definitions in this section can be overriden on the command line or in the # environment. # # These are equivalent: # # export PLAT=linux DEBUG=DEBUG # make # # and # # make PLAT=linux DEBUG=DEBUG # PLAT: bsd linux macosx win32 win64 mingw # platform to build for PLAT?=linux # LUAV: 5.1 5.2 # lua version to build against LUAV?=5.1 # MYCFLAGS: to be set by user if needed MYCFLAGS?= # MYLDFLAGS: to be set by user if needed MYLDFLAGS?= # DEBUG: NODEBUG DEBUG # debug mode causes luasocket to collect and return timing information useful # for testing and debugging luasocket itself DEBUG?=NODEBUG # DESTDIR: (no default) # used by package managers to install into a temporary destination DESTDIR?= #------ # Definitions below can be overridden on the make command line, but # shouldn't have to be. print: @echo PLAT=$(PLAT) @echo DEBUG=$(DEBUG) @echo CFLAGS=$(CFLAGS) @echo LDFLAGS=$(LDFLAGS) #------ # Supported platforms # PLATS= macosx linux win32 win64 mingw solaris #------ # Compiler and linker settings # for Mac OS X SO_macosx=so O_macosx=o CC_macosx=gcc DEF_macosx= -DLUASOCKET_$(DEBUG) -DUNIX_HAS_SUN_LEN CFLAGS_macosx=-g -O2 -Wall -fno-common $(DEF) LDFLAGS_macosx= -bundle -undefined dynamic_lookup -o LD_macosx=gcc SOCKET_macosx=usocket.o #------ # Compiler and linker settings # for Linux SO_linux=so O_linux=o CC_linux=gcc DEF_linux=-DLUASOCKET_$(DEBUG) CFLAGS_linux= -g -O2 -Wall -Wpedantic -Wextra $(DEF) LDFLAGS_linux= -o LD_linux=ld SOCKET_linux=usocket.o #------ # Compiler and linker settings # for BSD SO_bsd=so O_bsd=o CC_bsd=gcc DEF_bsd=-DLUASOCKET_$(DEBUG) -DUNIX_HAS_SUN_LEN CFLAGS_bsd=-g -O2 -Wall -Wextra -Wimplicit $(DEF) LDFLAGS_bsd=-O -shared -fpic -o LD_bsd=gcc SOCKET_bsd=usocket.o #------ # Compiler and linker settings # for Solaris SO_solaris=so O_solaris=o CC_solaris=gcc DEF_solaris=-DLUASOCKET_$(DEBUG) CFLAGS_solaris=-g -O2 -Wall -Wextra -Wimplicit $(DEF) LDFLAGS_solaris=-lnsl -lsocket -lresolv -O -shared -fpic -o LD_solaris=gcc SOCKET_solaris=usocket.o #------ # Compiler and linker settings # for MingW SO_mingw=dll O_mingw=o CC_mingw=gcc DEF_mingw= -DLUASOCKET_$(DEBUG) -DWINVER=0x0501 CFLAGS_mingw= -O2 -Wall -fno-common $(DEF) LDFLAGS_mingw= -shared -Wl,-s -lws2_32 -o LD_mingw=gcc SOCKET_mingw=wsocket.o #------ # Compiler and linker settings # for Win32 SO_win32=dll O_win32=obj CC_win32=cl DEF_win32= //D "WIN32" //D "NDEBUG" //D "_WINDOWS" //D "_USRDLL" \ //D "_CRT_SECURE_NO_WARNINGS" \ //D "_WINDLL" \ //D "LUASOCKET_$(DEBUG)" CFLAGS_win32=$(DEF) //O2 //Ot //MD //W3 //nologo LDFLAGS_win32= //nologo //link //NOLOGO //DLL //INCREMENTAL:NO \ //MANIFEST //MANIFESTFILE:"intermediate.manifest" \ /MANIFESTUAC:"level='asInvoker' uiAccess='false'" \ //SUBSYSTEM:WINDOWS //OPT:REF //OPT:ICF //DYNAMICBASE:NO \ //MACHINE:X86 \ ws2_32.lib //OUT: LD_win32=cl SOCKET_win32=wsocket.obj #------ # Compiler and linker settings # for Win64 SO_win64=dll O_win64=obj CC_win64=cl DEF_win64= //D "WIN32" //D "NDEBUG" //D "_WINDOWS" //D "_USRDLL" \ //D "_CRT_SECURE_NO_WARNINGS" \ //D "_WINDLL" \ //D "LUASOCKET_$(DEBUG)" CFLAGS_win64=$(DEF) //O2 //Ot //MD //W3 //nologo LDFLAGS_win64= //nologo //link //NOLOGO //DLL //INCREMENTAL:NO \ //MANIFEST //MANIFESTFILE:"intermediate.manifest" \ /MANIFESTUAC:"level='asInvoker' uiAccess='false'" \ //SUBSYSTEM:WINDOWS //OPT:REF //OPT:ICF //DYNAMICBASE:NO \ ws2_32.lib //OUT: LD_win64=cl SOCKET_win64=wsocket.obj .SUFFIXES: .obj .c.obj: $(CC) $(CFLAGS) //Fo"$@" //c $< #------ # Output file names # SO=$(SO_$(PLAT)) O=$(O_$(PLAT)) A=a SOCKET_A=socket.$(A) MIME_A=mime.$(A) UNIX_A=unix.$(A) SERIAL_A=serial.$(A) SOCKET=$(SOCKET_$(PLAT)) #------ # Settings selected for platform # CC=$(CC_$(PLAT)) DEF=$(DEF_$(PLAT)) AR= ar rc RANLIB= ranlib CFLAGS=$(CFLAGS_$(PLAT)) $(MYCFLAGS) LDFLAGS=$(LDFLAGS_$(PLAT)) $(MYLDFLAGS) LD=$(LD_$(PLAT)) #------ # Modules belonging to socket-core # SOCKET_OBJS= \ luasocket.$(O) \ timeout.$(O) \ buffer.$(O) \ io.$(O) \ auxiliar.$(O) \ compat.$(O) \ options.$(O) \ inet.$(O) \ $(SOCKET) \ except.$(O) \ select.$(O) \ tcp.$(O) \ udp.$(O) #------ # Modules belonging mime-core # MIME_OBJS= \ mime.$(O) \ compat.$(O) #------ # Modules belonging unix (local domain sockets) # UNIX_OBJS=\ buffer.$(O) \ auxiliar.$(O) \ options.$(O) \ timeout.$(O) \ io.$(O) \ usocket.$(O) \ unixstream.$(O) \ unixdgram.$(O) \ compat.$(O) \ unix.$(O) #------ # Modules belonging to serial (device streams) # SERIAL_OBJS=\ buffer.$(O) \ compat.$(O) \ auxiliar.$(O) \ options.$(O) \ timeout.$(O) \ io.$(O) \ usocket.$(O) \ serial.$(O) #------ # Files to install # TO_SOCKET_LDIR= \ http.lua \ url.lua \ tp.lua \ ftp.lua \ headers.lua \ smtp.lua TO_TOP_LDIR= \ ltn12.lua \ socket.lua \ mime.lua #------ # Targets # default: $(PLAT) bsd: $(MAKE) all-unix PLAT=bsd macosx: $(MAKE) all-unix PLAT=macosx win32: $(MAKE) all PLAT=win32 win64: $(MAKE) all PLAT=win64 linux: $(MAKE) all-unix PLAT=linux mingw: $(MAKE) all PLAT=mingw solaris: $(MAKE) all-unix PLAT=solaris none: @echo "Please run" @echo " make PLATFORM" @echo "where PLATFORM is one of these:" @echo " $(PLATS)" all: $(SOCKET_A) $(MIME_A) $(SOCKET_A): $(SOCKET_OBJS) $(AR) $@ $(SOCKET_OBJS) $(RANLIB) $@ $(MIME_A): $(MIME_OBJS) $(AR) $@ $(MIME_OBJS) $(RANLIB) $@ all-unix: all $(UNIX_SO) $(SERIAL_SO) $(UNIX_A): $(UNIX_OBJS) $(AR) $@ $(UNIX_OBJS) $(RANLIB) $@ $(SERIAL_A): $(SERIAL_OBJS) $(AR) $@ $(SERIAL_OBJS) $(RANLIB) $@ clean: rm -f $(SOCKET_A) $(SOCKET_OBJS) $(SERIAL_OBJS) rm -f $(MIME_A) $(UNIX_A) $(SERIAL_A) $(MIME_OBJS) $(UNIX_OBJS) .PHONY: all $(PLATS) default clean echo none #------ # List of dependencies # compat.$(O): compat.c compat.h auxiliar.$(O): auxiliar.c auxiliar.h buffer.$(O): buffer.c buffer.h io.h timeout.h except.$(O): except.c except.h inet.$(O): inet.c inet.h socket.h io.h timeout.h usocket.h io.$(O): io.c io.h timeout.h luasocket.$(O): luasocket.c luasocket.h auxiliar.h except.h \ timeout.h buffer.h io.h inet.h socket.h usocket.h tcp.h \ udp.h select.h mime.$(O): mime.c mime.h options.$(O): options.c auxiliar.h options.h socket.h io.h \ timeout.h usocket.h inet.h select.$(O): select.c socket.h io.h timeout.h usocket.h select.h serial.$(O): serial.c auxiliar.h socket.h io.h timeout.h usocket.h \ options.h unix.h buffer.h tcp.$(O): tcp.c auxiliar.h socket.h io.h timeout.h usocket.h \ inet.h options.h tcp.h buffer.h timeout.$(O): timeout.c auxiliar.h timeout.h udp.$(O): udp.c auxiliar.h socket.h io.h timeout.h usocket.h \ inet.h options.h udp.h unix.$(O): unix.c auxiliar.h socket.h io.h timeout.h usocket.h \ options.h unix.h buffer.h usocket.$(O): usocket.c socket.h io.h timeout.h usocket.h wsocket.$(O): wsocket.c socket.h io.h timeout.h usocket.h ================================================ FILE: src/luasocket/mbox.lua ================================================ local _M = {} if module then mbox = _M end function _M.split_message(message_s) local message = {} message_s = string.gsub(message_s, "\r\n", "\n") string.gsub(message_s, "^(.-\n)\n", function (h) message.headers = h end) string.gsub(message_s, "^.-\n\n(.*)", function (b) message.body = b end) if not message.body then string.gsub(message_s, "^\n(.*)", function (b) message.body = b end) end if not message.headers and not message.body then message.headers = message_s end return message.headers or "", message.body or "" end function _M.split_headers(headers_s) local headers = {} headers_s = string.gsub(headers_s, "\r\n", "\n") headers_s = string.gsub(headers_s, "\n[ ]+", " ") string.gsub("\n" .. headers_s, "\n([^\n]+)", function (h) table.insert(headers, h) end) return headers end function _M.parse_header(header_s) header_s = string.gsub(header_s, "\n[ ]+", " ") header_s = string.gsub(header_s, "\n+", "") local _, __, name, value = string.find(header_s, "([^%s:]-):%s*(.*)") return name, value end function _M.parse_headers(headers_s) local headers_t = _M.split_headers(headers_s) local headers = {} for i = 1, #headers_t do local name, value = _M.parse_header(headers_t[i]) if name then name = string.lower(name) if headers[name] then headers[name] = headers[name] .. ", " .. value else headers[name] = value end end end return headers end function _M.parse_from(from) local _, __, name, address = string.find(from, "^%s*(.-)%s*%<(.-)%>") if not address then _, __, address = string.find(from, "%s*(.+)%s*") end name = name or "" address = address or "" if name == "" then name = address end name = string.gsub(name, '"', "") return name, address end function _M.split_mbox(mbox_s) local mbox = {} mbox_s = string.gsub(mbox_s, "\r\n", "\n") .."\n\nFrom \n" local nj, i, j = 1, 1, 1 while 1 do i, nj = string.find(mbox_s, "\n\nFrom .-\n", j) if not i then break end local message = string.sub(mbox_s, j, i-1) table.insert(mbox, message) j = nj+1 end return mbox end function _M.parse(mbox_s) local mbox = _M.split_mbox(mbox_s) for i = 1, #mbox do mbox[i] = _M.parse_message(mbox[i]) end return mbox end function _M.parse_message(message_s) local message = {} message.headers, message.body = _M.split_message(message_s) message.headers = _M.parse_headers(message.headers) return message end return _M ================================================ FILE: src/luasocket/mime.c ================================================ /*=========================================================================*\ * MIME support functions * LuaSocket toolkit \*=========================================================================*/ #include "luasocket.h" #include "mime.h" #include #include /*=========================================================================*\ * Don't want to trust escape character constants \*=========================================================================*/ typedef unsigned char UC; static const char CRLF[] = "\r\n"; static const char EQCRLF[] = "=\r\n"; /*=========================================================================*\ * Internal function prototypes. \*=========================================================================*/ static int mime_global_wrp(lua_State *L); static int mime_global_b64(lua_State *L); static int mime_global_unb64(lua_State *L); static int mime_global_qp(lua_State *L); static int mime_global_unqp(lua_State *L); static int mime_global_qpwrp(lua_State *L); static int mime_global_eol(lua_State *L); static int mime_global_dot(lua_State *L); static size_t dot(int c, size_t state, luaL_Buffer *buffer); //static void b64setup(UC *base); static size_t b64encode(UC c, UC *input, size_t size, luaL_Buffer *buffer); static size_t b64pad(const UC *input, size_t size, luaL_Buffer *buffer); static size_t b64decode(UC c, UC *input, size_t size, luaL_Buffer *buffer); //static void qpsetup(UC *class, UC *unbase); static void qpquote(UC c, luaL_Buffer *buffer); static size_t qpdecode(UC c, UC *input, size_t size, luaL_Buffer *buffer); static size_t qpencode(UC c, UC *input, size_t size, const char *marker, luaL_Buffer *buffer); static size_t qppad(UC *input, size_t size, luaL_Buffer *buffer); /* code support functions */ static luaL_Reg func[] = { { "dot", mime_global_dot }, { "b64", mime_global_b64 }, { "eol", mime_global_eol }, { "qp", mime_global_qp }, { "qpwrp", mime_global_qpwrp }, { "unb64", mime_global_unb64 }, { "unqp", mime_global_unqp }, { "wrp", mime_global_wrp }, { NULL, NULL } }; /*-------------------------------------------------------------------------*\ * Quoted-printable globals \*-------------------------------------------------------------------------*/ enum {QP_PLAIN, QP_QUOTED, QP_CR, QP_IF_LAST}; static const UC qpclass[] = { QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_IF_LAST, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_CR, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_IF_LAST, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_QUOTED, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED }; static const UC qpbase[] = "0123456789ABCDEF"; static const UC qpunbase[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }; /*-------------------------------------------------------------------------*\ * Base64 globals \*-------------------------------------------------------------------------*/ static const UC b64base[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static const UC b64unbase[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 0, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }; /*=========================================================================*\ * Exported functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ LUASOCKET_API int luaopen_mime_core(lua_State *L) { lua_newtable(L); luaL_setfuncs(L, func, 0); lua_pushvalue(L, -1); lua_setglobal(L, "mime"); /* make version string available to scripts */ lua_pushstring(L, "_VERSION"); lua_pushstring(L, MIME_VERSION); lua_rawset(L, -3); /* initialize lookup tables */ // qpsetup(qpclass, qpunbase); // b64setup(b64unbase); return 1; } /*=========================================================================*\ * Global Lua functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Incrementaly breaks a string into lines. The string can have CRLF breaks. * A, n = wrp(l, B, length) * A is a copy of B, broken into lines of at most 'length' bytes. * 'l' is how many bytes are left for the first line of B. * 'n' is the number of bytes left in the last line of A. \*-------------------------------------------------------------------------*/ static int mime_global_wrp(lua_State *L) { size_t size = 0; int left = (int) luaL_checknumber(L, 1); const UC *input = (const UC *) luaL_optlstring(L, 2, NULL, &size); const UC *last = input + size; int length = (int) luaL_optnumber(L, 3, 76); luaL_Buffer buffer; /* end of input black-hole */ if (!input) { /* if last line has not been terminated, add a line break */ if (left < length) lua_pushstring(L, CRLF); /* otherwise, we are done */ else lua_pushnil(L); lua_pushnumber(L, length); return 2; } luaL_buffinit(L, &buffer); while (input < last) { switch (*input) { case '\r': break; case '\n': luaL_addstring(&buffer, CRLF); left = length; break; default: if (left <= 0) { left = length; luaL_addstring(&buffer, CRLF); } luaL_addchar(&buffer, *input); left--; break; } input++; } luaL_pushresult(&buffer); lua_pushnumber(L, left); return 2; } #if 0 /*-------------------------------------------------------------------------*\ * Fill base64 decode map. \*-------------------------------------------------------------------------*/ static void b64setup(UC *unbase) { int i; for (i = 0; i <= 255; i++) unbase[i] = (UC) 255; for (i = 0; i < 64; i++) unbase[b64base[i]] = (UC) i; unbase['='] = 0; printf("static const UC b64unbase[] = {\n"); for (int i = 0; i < 256; i++) { printf("%d, ", unbase[i]); } printf("\n}\n;"); } #endif /*-------------------------------------------------------------------------*\ * Acumulates bytes in input buffer until 3 bytes are available. * Translate the 3 bytes into Base64 form and append to buffer. * Returns new number of bytes in buffer. \*-------------------------------------------------------------------------*/ static size_t b64encode(UC c, UC *input, size_t size, luaL_Buffer *buffer) { input[size++] = c; if (size == 3) { UC code[4]; unsigned long value = 0; value += input[0]; value <<= 8; value += input[1]; value <<= 8; value += input[2]; code[3] = b64base[value & 0x3f]; value >>= 6; code[2] = b64base[value & 0x3f]; value >>= 6; code[1] = b64base[value & 0x3f]; value >>= 6; code[0] = b64base[value]; luaL_addlstring(buffer, (char *) code, 4); size = 0; } return size; } /*-------------------------------------------------------------------------*\ * Encodes the Base64 last 1 or 2 bytes and adds padding '=' * Result, if any, is appended to buffer. * Returns 0. \*-------------------------------------------------------------------------*/ static size_t b64pad(const UC *input, size_t size, luaL_Buffer *buffer) { unsigned long value = 0; UC code[4] = {'=', '=', '=', '='}; switch (size) { case 1: value = input[0] << 4; code[1] = b64base[value & 0x3f]; value >>= 6; code[0] = b64base[value]; luaL_addlstring(buffer, (char *) code, 4); break; case 2: value = input[0]; value <<= 8; value |= input[1]; value <<= 2; code[2] = b64base[value & 0x3f]; value >>= 6; code[1] = b64base[value & 0x3f]; value >>= 6; code[0] = b64base[value]; luaL_addlstring(buffer, (char *) code, 4); break; default: break; } return 0; } /*-------------------------------------------------------------------------*\ * Acumulates bytes in input buffer until 4 bytes are available. * Translate the 4 bytes from Base64 form and append to buffer. * Returns new number of bytes in buffer. \*-------------------------------------------------------------------------*/ static size_t b64decode(UC c, UC *input, size_t size, luaL_Buffer *buffer) { /* ignore invalid characters */ if (b64unbase[c] > 64) return size; input[size++] = c; /* decode atom */ if (size == 4) { UC decoded[3]; int valid, value = 0; value = b64unbase[input[0]]; value <<= 6; value |= b64unbase[input[1]]; value <<= 6; value |= b64unbase[input[2]]; value <<= 6; value |= b64unbase[input[3]]; decoded[2] = (UC) (value & 0xff); value >>= 8; decoded[1] = (UC) (value & 0xff); value >>= 8; decoded[0] = (UC) value; /* take care of paddding */ valid = (input[2] == '=') ? 1 : (input[3] == '=') ? 2 : 3; luaL_addlstring(buffer, (char *) decoded, valid); return 0; /* need more data */ } else return size; } /*-------------------------------------------------------------------------*\ * Incrementally applies the Base64 transfer content encoding to a string * A, B = b64(C, D) * A is the encoded version of the largest prefix of C .. D that is * divisible by 3. B has the remaining bytes of C .. D, *without* encoding. * The easiest thing would be to concatenate the two strings and * encode the result, but we can't afford that or Lua would dupplicate * every chunk we received. \*-------------------------------------------------------------------------*/ static int mime_global_b64(lua_State *L) { UC atom[3]; size_t isize = 0, asize = 0; const UC *input = (const UC *) luaL_optlstring(L, 1, NULL, &isize); const UC *last = input + isize; luaL_Buffer buffer; /* end-of-input blackhole */ if (!input) { lua_pushnil(L); lua_pushnil(L); return 2; } /* make sure we don't confuse buffer stuff with arguments */ lua_settop(L, 2); /* process first part of the input */ luaL_buffinit(L, &buffer); while (input < last) asize = b64encode(*input++, atom, asize, &buffer); input = (const UC *) luaL_optlstring(L, 2, NULL, &isize); /* if second part is nil, we are done */ if (!input) { size_t osize = 0; asize = b64pad(atom, asize, &buffer); luaL_pushresult(&buffer); /* if the output is empty and the input is nil, return nil */ lua_tolstring(L, -1, &osize); if (osize == 0) lua_pushnil(L); lua_pushnil(L); return 2; } /* otherwise process the second part */ last = input + isize; while (input < last) asize = b64encode(*input++, atom, asize, &buffer); luaL_pushresult(&buffer); lua_pushlstring(L, (char *) atom, asize); return 2; } /*-------------------------------------------------------------------------*\ * Incrementally removes the Base64 transfer content encoding from a string * A, B = b64(C, D) * A is the encoded version of the largest prefix of C .. D that is * divisible by 4. B has the remaining bytes of C .. D, *without* encoding. \*-------------------------------------------------------------------------*/ static int mime_global_unb64(lua_State *L) { UC atom[4]; size_t isize = 0, asize = 0; const UC *input = (const UC *) luaL_optlstring(L, 1, NULL, &isize); const UC *last = input + isize; luaL_Buffer buffer; /* end-of-input blackhole */ if (!input) { lua_pushnil(L); lua_pushnil(L); return 2; } /* make sure we don't confuse buffer stuff with arguments */ lua_settop(L, 2); /* process first part of the input */ luaL_buffinit(L, &buffer); while (input < last) asize = b64decode(*input++, atom, asize, &buffer); input = (const UC *) luaL_optlstring(L, 2, NULL, &isize); /* if second is nil, we are done */ if (!input) { size_t osize = 0; luaL_pushresult(&buffer); /* if the output is empty and the input is nil, return nil */ lua_tolstring(L, -1, &osize); if (osize == 0) lua_pushnil(L); lua_pushnil(L); return 2; } /* otherwise, process the rest of the input */ last = input + isize; while (input < last) asize = b64decode(*input++, atom, asize, &buffer); luaL_pushresult(&buffer); lua_pushlstring(L, (char *) atom, asize); return 2; } /*-------------------------------------------------------------------------*\ * Quoted-printable encoding scheme * all (except CRLF in text) can be =XX * CLRL in not text must be =XX=XX * 33 through 60 inclusive can be plain * 62 through 126 inclusive can be plain * 9 and 32 can be plain, unless in the end of a line, where must be =XX * encoded lines must be no longer than 76 not counting CRLF * soft line-break are =CRLF * To encode one byte, we need to see the next two. * Worst case is when we see a space, and wonder if a CRLF is comming \*-------------------------------------------------------------------------*/ #if 0 /*-------------------------------------------------------------------------*\ * Split quoted-printable characters into classes * Precompute reverse map for encoding \*-------------------------------------------------------------------------*/ static void qpsetup(UC *cl, UC *unbase) { int i; for (i = 0; i < 256; i++) cl[i] = QP_QUOTED; for (i = 33; i <= 60; i++) cl[i] = QP_PLAIN; for (i = 62; i <= 126; i++) cl[i] = QP_PLAIN; cl['\t'] = QP_IF_LAST; cl[' '] = QP_IF_LAST; cl['\r'] = QP_CR; for (i = 0; i < 256; i++) unbase[i] = 255; unbase['0'] = 0; unbase['1'] = 1; unbase['2'] = 2; unbase['3'] = 3; unbase['4'] = 4; unbase['5'] = 5; unbase['6'] = 6; unbase['7'] = 7; unbase['8'] = 8; unbase['9'] = 9; unbase['A'] = 10; unbase['a'] = 10; unbase['B'] = 11; unbase['b'] = 11; unbase['C'] = 12; unbase['c'] = 12; unbase['D'] = 13; unbase['d'] = 13; unbase['E'] = 14; unbase['e'] = 14; unbase['F'] = 15; unbase['f'] = 15; printf("static UC qpclass[] = {"); for (int i = 0; i < 256; i++) { if (i % 6 == 0) { printf("\n "); } switch(cl[i]) { case QP_QUOTED: printf("QP_QUOTED, "); break; case QP_PLAIN: printf("QP_PLAIN, "); break; case QP_CR: printf("QP_CR, "); break; case QP_IF_LAST: printf("QP_IF_LAST, "); break; } } printf("\n};\n"); printf("static const UC qpunbase[] = {"); for (int i = 0; i < 256; i++) { int c = qpunbase[i]; printf("%d, ", c); } printf("\";\n"); } #endif /*-------------------------------------------------------------------------*\ * Output one character in form =XX \*-------------------------------------------------------------------------*/ static void qpquote(UC c, luaL_Buffer *buffer) { luaL_addchar(buffer, '='); luaL_addchar(buffer, qpbase[c >> 4]); luaL_addchar(buffer, qpbase[c & 0x0F]); } /*-------------------------------------------------------------------------*\ * Accumulate characters until we are sure about how to deal with them. * Once we are sure, output to the buffer, in the correct form. \*-------------------------------------------------------------------------*/ static size_t qpencode(UC c, UC *input, size_t size, const char *marker, luaL_Buffer *buffer) { input[size++] = c; /* deal with all characters we can have */ while (size > 0) { switch (qpclass[input[0]]) { /* might be the CR of a CRLF sequence */ case QP_CR: if (size < 2) return size; if (input[1] == '\n') { luaL_addstring(buffer, marker); return 0; } else qpquote(input[0], buffer); break; /* might be a space and that has to be quoted if last in line */ case QP_IF_LAST: if (size < 3) return size; /* if it is the last, quote it and we are done */ if (input[1] == '\r' && input[2] == '\n') { qpquote(input[0], buffer); luaL_addstring(buffer, marker); return 0; } else luaL_addchar(buffer, input[0]); break; /* might have to be quoted always */ case QP_QUOTED: qpquote(input[0], buffer); break; /* might never have to be quoted */ default: luaL_addchar(buffer, input[0]); break; } input[0] = input[1]; input[1] = input[2]; size--; } return 0; } /*-------------------------------------------------------------------------*\ * Deal with the final characters \*-------------------------------------------------------------------------*/ static size_t qppad(UC *input, size_t size, luaL_Buffer *buffer) { size_t i; for (i = 0; i < size; i++) { if (qpclass[input[i]] == QP_PLAIN) luaL_addchar(buffer, input[i]); else qpquote(input[i], buffer); } if (size > 0) luaL_addstring(buffer, EQCRLF); return 0; } /*-------------------------------------------------------------------------*\ * Incrementally converts a string to quoted-printable * A, B = qp(C, D, marker) * Marker is the text to be used to replace CRLF sequences found in A. * A is the encoded version of the largest prefix of C .. D that * can be encoded without doubts. * B has the remaining bytes of C .. D, *without* encoding. \*-------------------------------------------------------------------------*/ static int mime_global_qp(lua_State *L) { size_t asize = 0, isize = 0; UC atom[3]; const UC *input = (const UC *) luaL_optlstring(L, 1, NULL, &isize); const UC *last = input + isize; const char *marker = luaL_optstring(L, 3, CRLF); luaL_Buffer buffer; /* end-of-input blackhole */ if (!input) { lua_pushnil(L); lua_pushnil(L); return 2; } /* make sure we don't confuse buffer stuff with arguments */ lua_settop(L, 3); /* process first part of input */ luaL_buffinit(L, &buffer); while (input < last) asize = qpencode(*input++, atom, asize, marker, &buffer); input = (const UC *) luaL_optlstring(L, 2, NULL, &isize); /* if second part is nil, we are done */ if (!input) { asize = qppad(atom, asize, &buffer); luaL_pushresult(&buffer); if (!(*lua_tostring(L, -1))) lua_pushnil(L); lua_pushnil(L); return 2; } /* otherwise process rest of input */ last = input + isize; while (input < last) asize = qpencode(*input++, atom, asize, marker, &buffer); luaL_pushresult(&buffer); lua_pushlstring(L, (char *) atom, asize); return 2; } /*-------------------------------------------------------------------------*\ * Accumulate characters until we are sure about how to deal with them. * Once we are sure, output the to the buffer, in the correct form. \*-------------------------------------------------------------------------*/ static size_t qpdecode(UC c, UC *input, size_t size, luaL_Buffer *buffer) { int d; input[size++] = c; /* deal with all characters we can deal */ switch (input[0]) { /* if we have an escape character */ case '=': if (size < 3) return size; /* eliminate soft line break */ if (input[1] == '\r' && input[2] == '\n') return 0; /* decode quoted representation */ c = qpunbase[input[1]]; d = qpunbase[input[2]]; /* if it is an invalid, do not decode */ if (c > 15 || d > 15) luaL_addlstring(buffer, (char *)input, 3); else luaL_addchar(buffer, (char) ((c << 4) + d)); return 0; case '\r': if (size < 2) return size; if (input[1] == '\n') luaL_addlstring(buffer, (char *)input, 2); return 0; default: if (input[0] == '\t' || (input[0] > 31 && input[0] < 127)) luaL_addchar(buffer, input[0]); return 0; } } /*-------------------------------------------------------------------------*\ * Incrementally decodes a string in quoted-printable * A, B = qp(C, D) * A is the decoded version of the largest prefix of C .. D that * can be decoded without doubts. * B has the remaining bytes of C .. D, *without* decoding. \*-------------------------------------------------------------------------*/ static int mime_global_unqp(lua_State *L) { size_t asize = 0, isize = 0; UC atom[3]; const UC *input = (const UC *) luaL_optlstring(L, 1, NULL, &isize); const UC *last = input + isize; luaL_Buffer buffer; /* end-of-input blackhole */ if (!input) { lua_pushnil(L); lua_pushnil(L); return 2; } /* make sure we don't confuse buffer stuff with arguments */ lua_settop(L, 2); /* process first part of input */ luaL_buffinit(L, &buffer); while (input < last) asize = qpdecode(*input++, atom, asize, &buffer); input = (const UC *) luaL_optlstring(L, 2, NULL, &isize); /* if second part is nil, we are done */ if (!input) { luaL_pushresult(&buffer); if (!(*lua_tostring(L, -1))) lua_pushnil(L); lua_pushnil(L); return 2; } /* otherwise process rest of input */ last = input + isize; while (input < last) asize = qpdecode(*input++, atom, asize, &buffer); luaL_pushresult(&buffer); lua_pushlstring(L, (char *) atom, asize); return 2; } /*-------------------------------------------------------------------------*\ * Incrementally breaks a quoted-printed string into lines * A, n = qpwrp(l, B, length) * A is a copy of B, broken into lines of at most 'length' bytes. * 'l' is how many bytes are left for the first line of B. * 'n' is the number of bytes left in the last line of A. * There are two complications: lines can't be broken in the middle * of an encoded =XX, and there might be line breaks already \*-------------------------------------------------------------------------*/ static int mime_global_qpwrp(lua_State *L) { size_t size = 0; int left = (int) luaL_checknumber(L, 1); const UC *input = (const UC *) luaL_optlstring(L, 2, NULL, &size); const UC *last = input + size; int length = (int) luaL_optnumber(L, 3, 76); luaL_Buffer buffer; /* end-of-input blackhole */ if (!input) { if (left < length) lua_pushstring(L, EQCRLF); else lua_pushnil(L); lua_pushnumber(L, length); return 2; } /* process all input */ luaL_buffinit(L, &buffer); while (input < last) { switch (*input) { case '\r': break; case '\n': left = length; luaL_addstring(&buffer, CRLF); break; case '=': if (left <= 3) { left = length; luaL_addstring(&buffer, EQCRLF); } luaL_addchar(&buffer, *input); left--; break; default: if (left <= 1) { left = length; luaL_addstring(&buffer, EQCRLF); } luaL_addchar(&buffer, *input); left--; break; } input++; } luaL_pushresult(&buffer); lua_pushnumber(L, left); return 2; } /*-------------------------------------------------------------------------*\ * Here is what we do: \n, and \r are considered candidates for line * break. We issue *one* new line marker if any of them is seen alone, or * followed by a different one. That is, \n\n and \r\r will issue two * end of line markers each, but \r\n, \n\r etc will only issue *one* * marker. This covers Mac OS, Mac OS X, VMS, Unix and DOS, as well as * probably other more obscure conventions. * * c is the current character being processed * last is the previous character \*-------------------------------------------------------------------------*/ #define eolcandidate(c) (c == '\r' || c == '\n') static int eolprocess(int c, int last, const char *marker, luaL_Buffer *buffer) { if (eolcandidate(c)) { if (eolcandidate(last)) { if (c == last) luaL_addstring(buffer, marker); return 0; } else { luaL_addstring(buffer, marker); return c; } } else { luaL_addchar(buffer, (char) c); return 0; } } /*-------------------------------------------------------------------------*\ * Converts a string to uniform EOL convention. * A, n = eol(o, B, marker) * A is the converted version of the largest prefix of B that can be * converted unambiguously. 'o' is the context returned by the previous * call. 'n' is the new context. \*-------------------------------------------------------------------------*/ static int mime_global_eol(lua_State *L) { int ctx = (int) luaL_checkinteger(L, 1); size_t isize = 0; const char *input = luaL_optlstring(L, 2, NULL, &isize); const char *last = input + isize; const char *marker = luaL_optstring(L, 3, CRLF); luaL_Buffer buffer; luaL_buffinit(L, &buffer); /* end of input blackhole */ if (!input) { lua_pushnil(L); lua_pushnumber(L, 0); return 2; } /* process all input */ while (input < last) ctx = eolprocess(*input++, ctx, marker, &buffer); luaL_pushresult(&buffer); lua_pushnumber(L, ctx); return 2; } /*-------------------------------------------------------------------------*\ * Takes one byte and stuff it if needed. \*-------------------------------------------------------------------------*/ static size_t dot(int c, size_t state, luaL_Buffer *buffer) { luaL_addchar(buffer, (char) c); switch (c) { case '\r': return 1; case '\n': return (state == 1)? 2: 0; case '.': if (state == 2) luaL_addchar(buffer, '.'); /* Falls through. */ default: return 0; } } /*-------------------------------------------------------------------------*\ * Incrementally applies smtp stuffing to a string * A, n = dot(l, D) \*-------------------------------------------------------------------------*/ static int mime_global_dot(lua_State *L) { size_t isize = 0, state = (size_t) luaL_checknumber(L, 1); const char *input = luaL_optlstring(L, 2, NULL, &isize); const char *last = input + isize; luaL_Buffer buffer; /* end-of-input blackhole */ if (!input) { lua_pushnil(L); lua_pushnumber(L, 2); return 2; } /* process all input */ luaL_buffinit(L, &buffer); while (input < last) state = dot(*input++, state, &buffer); luaL_pushresult(&buffer); lua_pushnumber(L, (lua_Number) state); return 2; } ================================================ FILE: src/luasocket/mime.h ================================================ #ifndef MIME_H #define MIME_H /*=========================================================================*\ * Core MIME support * LuaSocket toolkit * * This module provides functions to implement transfer content encodings * and formatting conforming to RFC 2045. It is used by mime.lua, which * provide a higher level interface to this functionality. \*=========================================================================*/ #include "luasocket.h" /*-------------------------------------------------------------------------*\ * Current MIME library version \*-------------------------------------------------------------------------*/ #define MIME_VERSION "MIME 1.0.3" #define MIME_COPYRIGHT "Copyright (C) 2004-2013 Diego Nehab" #define MIME_AUTHORS "Diego Nehab" LUASOCKET_API int luaopen_mime_core(lua_State *L); #endif /* MIME_H */ ================================================ FILE: src/luasocket/mime.lua ================================================ ----------------------------------------------------------------------------- -- MIME support for the Lua language. -- Author: Diego Nehab -- Conforming to RFCs 2045-2049 ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- -- Declare module and import dependencies ----------------------------------------------------------------------------- local _M = mime -- encode, decode and wrap algorithm tables local encodet, decodet, wrapt = {},{},{} _M.encodet = encodet _M.decodet = decodet _M.wrapt = wrapt -- creates a function that chooses a filter by name from a given table local function choose(table) return function(name, opt1, opt2) if type(name) ~= "string" then name, opt1, opt2 = "default", name, opt1 end local f = table[name or "nil"] if not f then error("unknown key (" .. tostring(name) .. ")", 3) else return f(opt1, opt2) end end end -- define the encoding filters encodet['base64'] = function() return ltn12.filter.cycle(_M.b64, "") end encodet['quoted-printable'] = function(mode) return ltn12.filter.cycle(_M.qp, "", (mode == "binary") and "=0D=0A" or "\r\n") end -- define the decoding filters decodet['base64'] = function() return ltn12.filter.cycle(_M.unb64, "") end decodet['quoted-printable'] = function() return ltn12.filter.cycle(_M.unqp, "") end local function format(chunk) if chunk then if chunk == "" then return "''" else return string.len(chunk) end else return "nil" end end -- define the line-wrap filters wrapt['text'] = function(length) length = length or 76 return ltn12.filter.cycle(_M.wrp, length, length) end wrapt['base64'] = wrapt['text'] wrapt['default'] = wrapt['text'] wrapt['quoted-printable'] = function() return ltn12.filter.cycle(_M.qpwrp, 76, 76) end -- function that choose the encoding, decoding or wrap algorithm _M.encode = choose(encodet) _M.decode = choose(decodet) _M.wrap = choose(wrapt) -- define the end-of-line normalization filter function _M.normalize(marker) return ltn12.filter.cycle(_M.eol, 0, marker) end -- high level stuffing filter function _M.stuff() return ltn12.filter.cycle(_M.dot, 2) end return _M ================================================ FILE: src/luasocket/options.c ================================================ /*=========================================================================*\ * Common option interface * LuaSocket toolkit \*=========================================================================*/ #include "luasocket.h" #include "auxiliar.h" #include "options.h" #include "inet.h" #include /*=========================================================================*\ * Internal functions prototypes \*=========================================================================*/ static int opt_setmembership(lua_State *L, p_socket ps, int level, int name); static int opt_ip6_setmembership(lua_State *L, p_socket ps, int level, int name); static int opt_setboolean(lua_State *L, p_socket ps, int level, int name); static int opt_getboolean(lua_State *L, p_socket ps, int level, int name); static int opt_setint(lua_State *L, p_socket ps, int level, int name); static int opt_getint(lua_State *L, p_socket ps, int level, int name); static int opt_set(lua_State *L, p_socket ps, int level, int name, void *val, int len); static int opt_get(lua_State *L, p_socket ps, int level, int name, void *val, int* len); /*=========================================================================*\ * Exported functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Calls appropriate option handler \*-------------------------------------------------------------------------*/ int opt_meth_setoption(lua_State *L, p_opt opt, p_socket ps) { const char *name = luaL_checkstring(L, 2); /* obj, name, ... */ while (opt->name && strcmp(name, opt->name)) opt++; if (!opt->func) { char msg[57]; sprintf(msg, "unsupported option `%.35s'", name); luaL_argerror(L, 2, msg); } return opt->func(L, ps); } int opt_meth_getoption(lua_State *L, p_opt opt, p_socket ps) { const char *name = luaL_checkstring(L, 2); /* obj, name, ... */ while (opt->name && strcmp(name, opt->name)) opt++; if (!opt->func) { char msg[57]; sprintf(msg, "unsupported option `%.35s'", name); luaL_argerror(L, 2, msg); } return opt->func(L, ps); } // ------------------------------------------------------- /* enables reuse of local address */ int opt_set_reuseaddr(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, SOL_SOCKET, SO_REUSEADDR); } int opt_get_reuseaddr(lua_State *L, p_socket ps) { return opt_getboolean(L, ps, SOL_SOCKET, SO_REUSEADDR); } // ------------------------------------------------------- /* enables reuse of local port */ int opt_set_reuseport(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, SOL_SOCKET, SO_REUSEPORT); } int opt_get_reuseport(lua_State *L, p_socket ps) { return opt_getboolean(L, ps, SOL_SOCKET, SO_REUSEPORT); } // ------------------------------------------------------- /* disables the Nagle algorithm */ int opt_set_tcp_nodelay(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, IPPROTO_TCP, TCP_NODELAY); } int opt_get_tcp_nodelay(lua_State *L, p_socket ps) { return opt_getboolean(L, ps, IPPROTO_TCP, TCP_NODELAY); } // ------------------------------------------------------- #ifdef TCP_KEEPIDLE int opt_get_tcp_keepidle(lua_State *L, p_socket ps) { return opt_getint(L, ps, IPPROTO_TCP, TCP_KEEPIDLE); } int opt_set_tcp_keepidle(lua_State *L, p_socket ps) { return opt_setint(L, ps, IPPROTO_TCP, TCP_KEEPIDLE); } #endif // ------------------------------------------------------- #ifdef TCP_KEEPCNT int opt_get_tcp_keepcnt(lua_State *L, p_socket ps) { return opt_getint(L, ps, IPPROTO_TCP, TCP_KEEPCNT); } int opt_set_tcp_keepcnt(lua_State *L, p_socket ps) { return opt_setint(L, ps, IPPROTO_TCP, TCP_KEEPCNT); } #endif // ------------------------------------------------------- #ifdef TCP_KEEPINTVL int opt_get_tcp_keepintvl(lua_State *L, p_socket ps) { return opt_getint(L, ps, IPPROTO_TCP, TCP_KEEPINTVL); } int opt_set_tcp_keepintvl(lua_State *L, p_socket ps) { return opt_setint(L, ps, IPPROTO_TCP, TCP_KEEPINTVL); } #endif // ------------------------------------------------------- int opt_set_keepalive(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, SOL_SOCKET, SO_KEEPALIVE); } int opt_get_keepalive(lua_State *L, p_socket ps) { return opt_getboolean(L, ps, SOL_SOCKET, SO_KEEPALIVE); } // ------------------------------------------------------- int opt_set_dontroute(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, SOL_SOCKET, SO_DONTROUTE); } int opt_get_dontroute(lua_State *L, p_socket ps) { return opt_getboolean(L, ps, SOL_SOCKET, SO_DONTROUTE); } // ------------------------------------------------------- int opt_set_broadcast(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, SOL_SOCKET, SO_BROADCAST); } int opt_get_broadcast(lua_State *L, p_socket ps) { return opt_getboolean(L, ps, SOL_SOCKET, SO_BROADCAST); } // ------------------------------------------------------- int opt_set_recv_buf_size(lua_State *L, p_socket ps) { return opt_setint(L, ps, SOL_SOCKET, SO_RCVBUF); } int opt_get_recv_buf_size(lua_State *L, p_socket ps) { return opt_getint(L, ps, SOL_SOCKET, SO_RCVBUF); } // ------------------------------------------------------- int opt_get_send_buf_size(lua_State *L, p_socket ps) { return opt_getint(L, ps, SOL_SOCKET, SO_SNDBUF); } int opt_set_send_buf_size(lua_State *L, p_socket ps) { return opt_setint(L, ps, SOL_SOCKET, SO_SNDBUF); } // ------------------------------------------------------- int opt_set_ip6_unicast_hops(lua_State *L, p_socket ps) { return opt_setint(L, ps, IPPROTO_IPV6, IPV6_UNICAST_HOPS); } int opt_get_ip6_unicast_hops(lua_State *L, p_socket ps) { return opt_getint(L, ps, IPPROTO_IPV6, IPV6_UNICAST_HOPS); } // ------------------------------------------------------- int opt_set_ip6_multicast_hops(lua_State *L, p_socket ps) { return opt_setint(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_HOPS); } int opt_get_ip6_multicast_hops(lua_State *L, p_socket ps) { return opt_getint(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_HOPS); } // ------------------------------------------------------- int opt_set_ip_multicast_loop(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, IPPROTO_IP, IP_MULTICAST_LOOP); } int opt_get_ip_multicast_loop(lua_State *L, p_socket ps) { return opt_getboolean(L, ps, IPPROTO_IP, IP_MULTICAST_LOOP); } // ------------------------------------------------------- int opt_set_ip6_multicast_loop(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); } int opt_get_ip6_multicast_loop(lua_State *L, p_socket ps) { return opt_getboolean(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); } // ------------------------------------------------------- int opt_set_linger(lua_State *L, p_socket ps) { struct linger li; /* obj, name, table */ if (!lua_istable(L, 3)) auxiliar_typeerror(L,3,lua_typename(L, LUA_TTABLE)); lua_pushstring(L, "on"); lua_gettable(L, 3); if (!lua_isboolean(L, -1)) luaL_argerror(L, 3, "boolean 'on' field expected"); li.l_onoff = (u_short) lua_toboolean(L, -1); lua_pushstring(L, "timeout"); lua_gettable(L, 3); if (!lua_isnumber(L, -1)) luaL_argerror(L, 3, "number 'timeout' field expected"); li.l_linger = (u_short) lua_tonumber(L, -1); return opt_set(L, ps, SOL_SOCKET, SO_LINGER, (char *) &li, sizeof(li)); } int opt_get_linger(lua_State *L, p_socket ps) { struct linger li; /* obj, name */ int len = sizeof(li); int err = opt_get(L, ps, SOL_SOCKET, SO_LINGER, (char *) &li, &len); if (err) return err; lua_newtable(L); lua_pushboolean(L, li.l_onoff); lua_setfield(L, -2, "on"); lua_pushinteger(L, li.l_linger); lua_setfield(L, -2, "timeout"); return 1; } // ------------------------------------------------------- int opt_set_ip_multicast_ttl(lua_State *L, p_socket ps) { return opt_setint(L, ps, IPPROTO_IP, IP_MULTICAST_TTL); } // ------------------------------------------------------- int opt_set_ip_multicast_if(lua_State *L, p_socket ps) { const char *address = luaL_checkstring(L, 3); /* obj, name, ip */ struct in_addr val; val.s_addr = htonl(INADDR_ANY); if (strcmp(address, "*") && !inet_aton(address, &val)) luaL_argerror(L, 3, "ip expected"); return opt_set(L, ps, IPPROTO_IP, IP_MULTICAST_IF, (char *) &val, sizeof(val)); } int opt_get_ip_multicast_if(lua_State *L, p_socket ps) { struct in_addr val; socklen_t len = sizeof(val); if (getsockopt(*ps, IPPROTO_IP, IP_MULTICAST_IF, (char *) &val, &len) < 0) { lua_pushnil(L); lua_pushstring(L, "getsockopt failed"); return 2; } lua_pushstring(L, inet_ntoa(val)); return 1; } // ------------------------------------------------------- int opt_set_ip_add_membership(lua_State *L, p_socket ps) { return opt_setmembership(L, ps, IPPROTO_IP, IP_ADD_MEMBERSHIP); } int opt_set_ip_drop_membersip(lua_State *L, p_socket ps) { return opt_setmembership(L, ps, IPPROTO_IP, IP_DROP_MEMBERSHIP); } // ------------------------------------------------------- int opt_set_ip6_add_membership(lua_State *L, p_socket ps) { return opt_ip6_setmembership(L, ps, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP); } int opt_set_ip6_drop_membersip(lua_State *L, p_socket ps) { return opt_ip6_setmembership(L, ps, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP); } // ------------------------------------------------------- int opt_get_ip6_v6only(lua_State *L, p_socket ps) { return opt_getboolean(L, ps, IPPROTO_IPV6, IPV6_V6ONLY); } int opt_set_ip6_v6only(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, IPPROTO_IPV6, IPV6_V6ONLY); } // ------------------------------------------------------- int opt_get_error(lua_State *L, p_socket ps) { int val = 0; socklen_t len = sizeof(val); if (getsockopt(*ps, SOL_SOCKET, SO_ERROR, (char *) &val, &len) < 0) { lua_pushnil(L); lua_pushstring(L, "getsockopt failed"); return 2; } lua_pushstring(L, socket_strerror(val)); return 1; } /*=========================================================================*\ * Auxiliar functions \*=========================================================================*/ static int opt_setmembership(lua_State *L, p_socket ps, int level, int name) { struct ip_mreq val; /* obj, name, table */ if (!lua_istable(L, 3)) auxiliar_typeerror(L,3,lua_typename(L, LUA_TTABLE)); lua_pushstring(L, "multiaddr"); lua_gettable(L, 3); if (!lua_isstring(L, -1)) luaL_argerror(L, 3, "string 'multiaddr' field expected"); if (!inet_aton(lua_tostring(L, -1), &val.imr_multiaddr)) luaL_argerror(L, 3, "invalid 'multiaddr' ip address"); lua_pushstring(L, "interface"); lua_gettable(L, 3); if (!lua_isstring(L, -1)) luaL_argerror(L, 3, "string 'interface' field expected"); val.imr_interface.s_addr = htonl(INADDR_ANY); if (strcmp(lua_tostring(L, -1), "*") && !inet_aton(lua_tostring(L, -1), &val.imr_interface)) luaL_argerror(L, 3, "invalid 'interface' ip address"); return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); } static int opt_ip6_setmembership(lua_State *L, p_socket ps, int level, int name) { struct ipv6_mreq val; /* obj, opt-name, table */ memset(&val, 0, sizeof(val)); if (!lua_istable(L, 3)) auxiliar_typeerror(L,3,lua_typename(L, LUA_TTABLE)); lua_pushstring(L, "multiaddr"); lua_gettable(L, 3); if (!lua_isstring(L, -1)) luaL_argerror(L, 3, "string 'multiaddr' field expected"); if (!inet_pton(AF_INET6, lua_tostring(L, -1), &val.ipv6mr_multiaddr)) luaL_argerror(L, 3, "invalid 'multiaddr' ip address"); lua_pushstring(L, "interface"); lua_gettable(L, 3); /* By default we listen to interface on default route * (sigh). However, interface= can override it. We should * support either number, or name for it. Waiting for * windows port of if_nametoindex */ if (!lua_isnil(L, -1)) { if (lua_isnumber(L, -1)) { val.ipv6mr_interface = (unsigned int) lua_tonumber(L, -1); } else luaL_argerror(L, -1, "number 'interface' field expected"); } return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); } static int opt_get(lua_State *L, p_socket ps, int level, int name, void *val, int* len) { socklen_t socklen = *len; if (getsockopt(*ps, level, name, (char *) val, &socklen) < 0) { lua_pushnil(L); lua_pushstring(L, "getsockopt failed"); return 2; } *len = socklen; return 0; } static int opt_set(lua_State *L, p_socket ps, int level, int name, void *val, int len) { if (setsockopt(*ps, level, name, (char *) val, len) < 0) { lua_pushnil(L); lua_pushstring(L, "setsockopt failed"); return 2; } lua_pushnumber(L, 1); return 1; } static int opt_getboolean(lua_State *L, p_socket ps, int level, int name) { int val = 0; int len = sizeof(val); int err = opt_get(L, ps, level, name, (char *) &val, &len); if (err) return err; lua_pushboolean(L, val); return 1; } static int opt_setboolean(lua_State *L, p_socket ps, int level, int name) { int val = auxiliar_checkboolean(L, 3); /* obj, name, bool */ return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); } static int opt_getint(lua_State *L, p_socket ps, int level, int name) { int val = 0; int len = sizeof(val); int err = opt_get(L, ps, level, name, (char *) &val, &len); if (err) return err; lua_pushnumber(L, val); return 1; } static int opt_setint(lua_State *L, p_socket ps, int level, int name) { int val = (int) lua_tonumber(L, 3); /* obj, name, int */ return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); } ================================================ FILE: src/luasocket/options.h ================================================ #ifndef OPTIONS_H #define OPTIONS_H /*=========================================================================*\ * Common option interface * LuaSocket toolkit * * This module provides a common interface to socket options, used mainly by * modules UDP and TCP. \*=========================================================================*/ #include "luasocket.h" #include "socket.h" /* option registry */ typedef struct t_opt { const char *name; int (*func)(lua_State *L, p_socket ps); } t_opt; typedef t_opt *p_opt; #ifndef _WIN32 #pragma GCC visibility push(hidden) #endif int opt_meth_setoption(lua_State *L, p_opt opt, p_socket ps); int opt_meth_getoption(lua_State *L, p_opt opt, p_socket ps); int opt_set_reuseaddr(lua_State *L, p_socket ps); int opt_get_reuseaddr(lua_State *L, p_socket ps); int opt_set_reuseport(lua_State *L, p_socket ps); int opt_get_reuseport(lua_State *L, p_socket ps); int opt_set_tcp_nodelay(lua_State *L, p_socket ps); int opt_get_tcp_nodelay(lua_State *L, p_socket ps); #ifdef TCP_KEEPIDLE int opt_set_tcp_keepidle(lua_State *L, p_socket ps); int opt_get_tcp_keepidle(lua_State *L, p_socket ps); #endif #ifdef TCP_KEEPCNT int opt_set_tcp_keepcnt(lua_State *L, p_socket ps); int opt_get_tcp_keepcnt(lua_State *L, p_socket ps); #endif #ifdef TCP_KEEPINTVL int opt_set_tcp_keepintvl(lua_State *L, p_socket ps); int opt_get_tcp_keepintvl(lua_State *L, p_socket ps); #endif int opt_set_keepalive(lua_State *L, p_socket ps); int opt_get_keepalive(lua_State *L, p_socket ps); int opt_set_dontroute(lua_State *L, p_socket ps); int opt_get_dontroute(lua_State *L, p_socket ps); int opt_set_broadcast(lua_State *L, p_socket ps); int opt_get_broadcast(lua_State *L, p_socket ps); int opt_set_recv_buf_size(lua_State *L, p_socket ps); int opt_get_recv_buf_size(lua_State *L, p_socket ps); int opt_set_send_buf_size(lua_State *L, p_socket ps); int opt_get_send_buf_size(lua_State *L, p_socket ps); int opt_set_ip6_unicast_hops(lua_State *L, p_socket ps); int opt_get_ip6_unicast_hops(lua_State *L, p_socket ps); int opt_set_ip6_multicast_hops(lua_State *L, p_socket ps); int opt_get_ip6_multicast_hops(lua_State *L, p_socket ps); int opt_set_ip_multicast_loop(lua_State *L, p_socket ps); int opt_get_ip_multicast_loop(lua_State *L, p_socket ps); int opt_set_ip6_multicast_loop(lua_State *L, p_socket ps); int opt_get_ip6_multicast_loop(lua_State *L, p_socket ps); int opt_set_linger(lua_State *L, p_socket ps); int opt_get_linger(lua_State *L, p_socket ps); int opt_set_ip_multicast_ttl(lua_State *L, p_socket ps); int opt_set_ip_multicast_if(lua_State *L, p_socket ps); int opt_get_ip_multicast_if(lua_State *L, p_socket ps); int opt_set_ip_add_membership(lua_State *L, p_socket ps); int opt_set_ip_drop_membersip(lua_State *L, p_socket ps); int opt_set_ip6_add_membership(lua_State *L, p_socket ps); int opt_set_ip6_drop_membersip(lua_State *L, p_socket ps); int opt_set_ip6_v6only(lua_State *L, p_socket ps); int opt_get_ip6_v6only(lua_State *L, p_socket ps); int opt_get_error(lua_State *L, p_socket ps); #ifndef _WIN32 #pragma GCC visibility pop #endif #endif ================================================ FILE: src/luasocket/pierror.h ================================================ #ifndef PIERROR_H #define PIERROR_H /*=========================================================================*\ * Error messages * Defines platform independent error messages \*=========================================================================*/ #define PIE_HOST_NOT_FOUND "host not found" #define PIE_ADDRINUSE "address already in use" #define PIE_ISCONN "already connected" #define PIE_ACCESS "permission denied" #define PIE_CONNREFUSED "connection refused" #define PIE_CONNABORTED "closed" #define PIE_CONNRESET "closed" #define PIE_TIMEDOUT "timeout" #define PIE_AGAIN "temporary failure in name resolution" #define PIE_BADFLAGS "invalid value for ai_flags" #define PIE_BADHINTS "invalid value for hints" #define PIE_FAIL "non-recoverable failure in name resolution" #define PIE_FAMILY "ai_family not supported" #define PIE_MEMORY "memory allocation failure" #define PIE_NONAME "host or service not provided, or not known" #define PIE_OVERFLOW "argument buffer overflow" #define PIE_PROTOCOL "resolved protocol is unknown" #define PIE_SERVICE "service not supported for socket type" #define PIE_SOCKTYPE "ai_socktype not supported" #endif ================================================ FILE: src/luasocket/select.c ================================================ /*=========================================================================*\ * Select implementation * LuaSocket toolkit \*=========================================================================*/ #include "luasocket.h" #include "socket.h" #include "timeout.h" #include "select.h" #include /*=========================================================================*\ * Internal function prototypes. \*=========================================================================*/ static t_socket getfd(lua_State *L); static int dirty(lua_State *L); static void collect_fd(lua_State *L, int tab, int itab, fd_set *set, t_socket *max_fd); static int check_dirty(lua_State *L, int tab, int dtab, fd_set *set); static void return_fd(lua_State *L, fd_set *set, t_socket max_fd, int itab, int tab, int start); static void make_assoc(lua_State *L, int tab); static int global_select(lua_State *L); /* functions in library namespace */ static luaL_Reg func[] = { {"select", global_select}, {NULL, NULL} }; /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ int select_open(lua_State *L) { lua_pushstring(L, "_SETSIZE"); lua_pushinteger(L, FD_SETSIZE); lua_rawset(L, -3); lua_pushstring(L, "_SOCKETINVALID"); lua_pushinteger(L, SOCKET_INVALID); lua_rawset(L, -3); luaL_setfuncs(L, func, 0); return 0; } /*=========================================================================*\ * Global Lua functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Waits for a set of sockets until a condition is met or timeout. \*-------------------------------------------------------------------------*/ static int global_select(lua_State *L) { int rtab, wtab, itab, ret, ndirty; t_socket max_fd = SOCKET_INVALID; fd_set rset, wset; t_timeout tm; double t = luaL_optnumber(L, 3, -1); FD_ZERO(&rset); FD_ZERO(&wset); lua_settop(L, 3); lua_newtable(L); itab = lua_gettop(L); lua_newtable(L); rtab = lua_gettop(L); lua_newtable(L); wtab = lua_gettop(L); collect_fd(L, 1, itab, &rset, &max_fd); collect_fd(L, 2, itab, &wset, &max_fd); ndirty = check_dirty(L, 1, rtab, &rset); t = ndirty > 0? 0.0: t; timeout_init(&tm, t, -1); timeout_markstart(&tm); ret = socket_select(max_fd+1, &rset, &wset, NULL, &tm); if (ret > 0 || ndirty > 0) { return_fd(L, &rset, max_fd+1, itab, rtab, ndirty); return_fd(L, &wset, max_fd+1, itab, wtab, 0); make_assoc(L, rtab); make_assoc(L, wtab); return 2; } else if (ret == 0) { lua_pushstring(L, "timeout"); return 3; } else { luaL_error(L, "select failed"); return 3; } } /*=========================================================================*\ * Internal functions \*=========================================================================*/ static t_socket getfd(lua_State *L) { t_socket fd = SOCKET_INVALID; lua_pushstring(L, "getfd"); lua_gettable(L, -2); if (!lua_isnil(L, -1)) { lua_pushvalue(L, -2); lua_call(L, 1, 1); if (lua_isnumber(L, -1)) { double numfd = lua_tonumber(L, -1); fd = (numfd >= 0.0)? (t_socket) numfd: SOCKET_INVALID; } } lua_pop(L, 1); return fd; } static int dirty(lua_State *L) { int is = 0; lua_pushstring(L, "dirty"); lua_gettable(L, -2); if (!lua_isnil(L, -1)) { lua_pushvalue(L, -2); lua_call(L, 1, 1); is = lua_toboolean(L, -1); } lua_pop(L, 1); return is; } static void collect_fd(lua_State *L, int tab, int itab, fd_set *set, t_socket *max_fd) { int i = 1, n = 0; /* nil is the same as an empty table */ if (lua_isnil(L, tab)) return; /* otherwise we need it to be a table */ luaL_checktype(L, tab, LUA_TTABLE); for ( ;; ) { t_socket fd; lua_pushnumber(L, i); lua_gettable(L, tab); if (lua_isnil(L, -1)) { lua_pop(L, 1); break; } /* getfd figures out if this is a socket */ fd = getfd(L); if (fd != SOCKET_INVALID) { /* make sure we don't overflow the fd_set */ #ifdef _WIN32 if (n >= FD_SETSIZE) luaL_argerror(L, tab, "too many sockets"); #else if (fd >= FD_SETSIZE) luaL_argerror(L, tab, "descriptor too large for set size"); #endif FD_SET(fd, set); n++; /* keep track of the largest descriptor so far */ if (*max_fd == SOCKET_INVALID || *max_fd < fd) *max_fd = fd; /* make sure we can map back from descriptor to the object */ lua_pushnumber(L, (lua_Number) fd); lua_pushvalue(L, -2); lua_settable(L, itab); } lua_pop(L, 1); i = i + 1; } } static int check_dirty(lua_State *L, int tab, int dtab, fd_set *set) { int ndirty = 0, i = 1; if (lua_isnil(L, tab)) return 0; for ( ;; ) { t_socket fd; lua_pushnumber(L, i); lua_gettable(L, tab); if (lua_isnil(L, -1)) { lua_pop(L, 1); break; } fd = getfd(L); if (fd != SOCKET_INVALID && dirty(L)) { lua_pushnumber(L, ++ndirty); lua_pushvalue(L, -2); lua_settable(L, dtab); FD_CLR(fd, set); } lua_pop(L, 1); i = i + 1; } return ndirty; } static void return_fd(lua_State *L, fd_set *set, t_socket max_fd, int itab, int tab, int start) { t_socket fd; for (fd = 0; fd < max_fd; fd++) { if (FD_ISSET(fd, set)) { lua_pushnumber(L, ++start); lua_pushnumber(L, (lua_Number) fd); lua_gettable(L, itab); lua_settable(L, tab); } } } static void make_assoc(lua_State *L, int tab) { int i = 1, atab; lua_newtable(L); atab = lua_gettop(L); for ( ;; ) { lua_pushnumber(L, i); lua_gettable(L, tab); if (!lua_isnil(L, -1)) { lua_pushnumber(L, i); lua_pushvalue(L, -2); lua_settable(L, atab); lua_pushnumber(L, i); lua_settable(L, atab); } else { lua_pop(L, 1); break; } i = i+1; } } ================================================ FILE: src/luasocket/select.h ================================================ #ifndef SELECT_H #define SELECT_H /*=========================================================================*\ * Select implementation * LuaSocket toolkit * * Each object that can be passed to the select function has to export * method getfd() which returns the descriptor to be passed to the * underlying select function. Another method, dirty(), should return * true if there is data ready for reading (required for buffered input). \*=========================================================================*/ #ifndef _WIN32 #pragma GCC visibility push(hidden) #endif int select_open(lua_State *L); #ifndef _WIN32 #pragma GCC visibility pop #endif #endif /* SELECT_H */ ================================================ FILE: src/luasocket/serial.c ================================================ /*=========================================================================*\ * Serial stream * LuaSocket toolkit \*=========================================================================*/ #include "luasocket.h" #include "auxiliar.h" #include "socket.h" #include "options.h" #include "unix.h" #include #include /* Reuses userdata definition from unix.h, since it is useful for all stream-like objects. If we stored the serial path for use in error messages or userdata printing, we might need our own userdata definition. Group usage is semi-inherited from unix.c, but unnecessary since we have only one object type. */ /*=========================================================================*\ * Internal function prototypes \*=========================================================================*/ static int global_create(lua_State *L); static int meth_send(lua_State *L); static int meth_receive(lua_State *L); static int meth_close(lua_State *L); static int meth_settimeout(lua_State *L); static int meth_getfd(lua_State *L); static int meth_setfd(lua_State *L); static int meth_dirty(lua_State *L); static int meth_getstats(lua_State *L); static int meth_setstats(lua_State *L); /* serial object methods */ static luaL_Reg serial_methods[] = { {"__gc", meth_close}, {"__tostring", auxiliar_tostring}, {"close", meth_close}, {"dirty", meth_dirty}, {"getfd", meth_getfd}, {"getstats", meth_getstats}, {"setstats", meth_setstats}, {"receive", meth_receive}, {"send", meth_send}, {"setfd", meth_setfd}, {"settimeout", meth_settimeout}, {NULL, NULL} }; /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ LUASOCKET_API int luaopen_socket_serial(lua_State *L) { /* create classes */ auxiliar_newclass(L, "serial{client}", serial_methods); /* create class groups */ auxiliar_add2group(L, "serial{client}", "serial{any}"); lua_pushcfunction(L, global_create); return 1; } /*=========================================================================*\ * Lua methods \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Just call buffered IO methods \*-------------------------------------------------------------------------*/ static int meth_send(lua_State *L) { p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); return buffer_meth_send(L, &un->buf); } static int meth_receive(lua_State *L) { p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); return buffer_meth_receive(L, &un->buf); } static int meth_getstats(lua_State *L) { p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); return buffer_meth_getstats(L, &un->buf); } static int meth_setstats(lua_State *L) { p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); return buffer_meth_setstats(L, &un->buf); } /*-------------------------------------------------------------------------*\ * Select support methods \*-------------------------------------------------------------------------*/ static int meth_getfd(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); lua_pushnumber(L, (int) un->sock); return 1; } /* this is very dangerous, but can be handy for those that are brave enough */ static int meth_setfd(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); un->sock = (t_socket) luaL_checknumber(L, 2); return 0; } static int meth_dirty(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); lua_pushboolean(L, !buffer_isempty(&un->buf)); return 1; } /*-------------------------------------------------------------------------*\ * Closes socket used by object \*-------------------------------------------------------------------------*/ static int meth_close(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); socket_destroy(&un->sock); lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Just call tm methods \*-------------------------------------------------------------------------*/ static int meth_settimeout(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); return timeout_meth_settimeout(L, &un->tm); } /*=========================================================================*\ * Library functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Creates a serial object \*-------------------------------------------------------------------------*/ static int global_create(lua_State *L) { const char* path = luaL_checkstring(L, 1); /* allocate unix object */ p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); /* open serial device */ t_socket sock = open(path, O_NOCTTY|O_RDWR); /*printf("open %s on %d\n", path, sock);*/ if (sock < 0) { lua_pushnil(L); lua_pushstring(L, socket_strerror(errno)); lua_pushnumber(L, errno); return 3; } /* set its type as client object */ auxiliar_setclass(L, "serial{client}", -1); /* initialize remaining structure fields */ socket_setnonblocking(&sock); un->sock = sock; io_init(&un->io, (p_send) socket_write, (p_recv) socket_read, (p_error) socket_ioerror, &un->sock); timeout_init(&un->tm, -1, -1); buffer_init(&un->buf, &un->io, &un->tm); return 1; } ================================================ FILE: src/luasocket/smtp.lua ================================================ ----------------------------------------------------------------------------- -- SMTP client support for the Lua language. -- LuaSocket toolkit. -- Author: Diego Nehab ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- -- Declare module and import dependencies ----------------------------------------------------------------------------- local tp = require("socket.tp") socket.smtp = {} local _M = socket.smtp ----------------------------------------------------------------------------- -- Program constants ----------------------------------------------------------------------------- -- timeout for connection _M.TIMEOUT = 60 -- default server used to send e-mails _M.SERVER = "localhost" -- default port _M.PORT = 25 -- domain used in HELO command and default sendmail -- If we are under a CGI, try to get from environment _M.DOMAIN = os.getenv("SERVER_NAME") or "localhost" -- default time zone (means we don't know) _M.ZONE = "-0000" --------------------------------------------------------------------------- -- Low level SMTP API ----------------------------------------------------------------------------- local metat = { __index = {} } function metat.__index:greet(domain) self.try(self.tp:check("2..")) self.try(self.tp:command("EHLO", domain or _M.DOMAIN)) return socket.skip(1, self.try(self.tp:check("2.."))) end function metat.__index:mail(from) self.try(self.tp:command("MAIL", "FROM:" .. from)) return self.try(self.tp:check("2..")) end function metat.__index:rcpt(to) self.try(self.tp:command("RCPT", "TO:" .. to)) return self.try(self.tp:check("2..")) end function metat.__index:data(src, step) self.try(self.tp:command("DATA")) self.try(self.tp:check("3..")) self.try(self.tp:source(src, step)) self.try(self.tp:send("\r\n.\r\n")) return self.try(self.tp:check("2..")) end function metat.__index:quit() self.try(self.tp:command("QUIT")) return self.try(self.tp:check("2..")) end function metat.__index:close() return self.tp:close() end function metat.__index:login(user, password) self.try(self.tp:command("AUTH", "LOGIN")) self.try(self.tp:check("3..")) self.try(self.tp:send(mime.b64(user) .. "\r\n")) self.try(self.tp:check("3..")) self.try(self.tp:send(mime.b64(password) .. "\r\n")) return self.try(self.tp:check("2..")) end function metat.__index:plain(user, password) local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password) self.try(self.tp:command("AUTH", auth)) return self.try(self.tp:check("2..")) end function metat.__index:auth(user, password, ext) if not user or not password then return 1 end if string.find(ext, "AUTH[^\n]+LOGIN") then return self:login(user, password) elseif string.find(ext, "AUTH[^\n]+PLAIN") then return self:plain(user, password) else self.try(nil, "authentication not supported") end end -- send message or throw an exception function metat.__index:send(mailt) self:mail(mailt.from) if type(mailt.rcpt) == "table" then for i,v in ipairs(mailt.rcpt) do self:rcpt(v) end else self:rcpt(mailt.rcpt) end self:data(ltn12.source.chain(mailt.source, mime.stuff()), mailt.step) end function _M.open(server, port, create) local tp = socket.try(tp.connect(server or _M.SERVER, port or _M.PORT, _M.TIMEOUT, create)) local s = setmetatable({tp = tp}, metat) -- make sure tp is closed if we get an exception s.try = socket.newtry(function() s:close() end) return s end -- convert headers to lowercase local function lower_headers(headers) local lower = {} for i,v in pairs(headers or lower) do lower[string.lower(i)] = v end return lower end --------------------------------------------------------------------------- -- Multipart message source ----------------------------------------------------------------------------- -- returns a hopefully unique mime boundary local seqno = 0 local function newboundary() seqno = seqno + 1 return string.format('%s%05d==%05u', os.date('%d%m%Y%H%M%S'), math.random(0, 99999), seqno) end -- send_message forward declaration local send_message -- yield the headers all at once, it's faster local function send_headers(tosend) local canonic = headers.canonic local h = "\r\n" for f,v in pairs(tosend) do h = (canonic[f] or f) .. ': ' .. v .. "\r\n" .. h end coroutine.yield(h) end -- yield multipart message body from a multipart message table local function send_multipart(mesgt) -- make sure we have our boundary and send headers local bd = newboundary() local headers = lower_headers(mesgt.headers or {}) headers['content-type'] = headers['content-type'] or 'multipart/mixed' headers['content-type'] = headers['content-type'] .. '; boundary="' .. bd .. '"' send_headers(headers) -- send preamble if mesgt.body.preamble then coroutine.yield(mesgt.body.preamble) coroutine.yield("\r\n") end -- send each part separated by a boundary for i, m in ipairs(mesgt.body) do coroutine.yield("\r\n--" .. bd .. "\r\n") send_message(m) end -- send last boundary coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n") -- send epilogue if mesgt.body.epilogue then coroutine.yield(mesgt.body.epilogue) coroutine.yield("\r\n") end end -- yield message body from a source local function send_source(mesgt) -- make sure we have a content-type local headers = lower_headers(mesgt.headers or {}) headers['content-type'] = headers['content-type'] or 'text/plain; charset="iso-8859-1"' send_headers(headers) -- send body from source while true do local chunk, err = mesgt.body() if err then coroutine.yield(nil, err) elseif chunk then coroutine.yield(chunk) else break end end end -- yield message body from a string local function send_string(mesgt) -- make sure we have a content-type local headers = lower_headers(mesgt.headers or {}) headers['content-type'] = headers['content-type'] or 'text/plain; charset="iso-8859-1"' send_headers(headers) -- send body from string coroutine.yield(mesgt.body) end -- message source function send_message(mesgt) if type(mesgt.body) == "table" then send_multipart(mesgt) elseif type(mesgt.body) == "function" then send_source(mesgt) else send_string(mesgt) end end -- set defaul headers local function adjust_headers(mesgt) local lower = lower_headers(mesgt.headers) lower["date"] = lower["date"] or os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or _M.ZONE) lower["x-mailer"] = lower["x-mailer"] or socket._VERSION -- this can't be overriden lower["mime-version"] = "1.0" return lower end function _M.message(mesgt) mesgt.headers = adjust_headers(mesgt) -- create and return message source local co = coroutine.create(function() send_message(mesgt) end) return function() local ret, a, b = coroutine.resume(co) if ret then return a, b else return nil, a end end end --------------------------------------------------------------------------- -- High level SMTP API ----------------------------------------------------------------------------- _M.send = socket.protect(function(mailt) local s = _M.open(mailt.server, mailt.port, mailt.create) local ext = s:greet(mailt.domain) s:auth(mailt.user, mailt.password, ext) s:send(mailt) s:quit() return s:close() end) return _M ================================================ FILE: src/luasocket/socket.h ================================================ #ifndef SOCKET_H #define SOCKET_H /*=========================================================================*\ * Socket compatibilization module * LuaSocket toolkit * * BSD Sockets and WinSock are similar, but there are a few irritating * differences. Also, not all *nix platforms behave the same. This module * (and the associated usocket.h and wsocket.h) factor these differences and * creates a interface compatible with the io.h module. \*=========================================================================*/ #include "io.h" /*=========================================================================*\ * Platform specific compatibilization \*=========================================================================*/ #ifdef _WIN32 #include "wsocket.h" #else #include "usocket.h" #endif /*=========================================================================*\ * The connect and accept functions accept a timeout and their * implementations are somewhat complicated. We chose to move * the timeout control into this module for these functions in * order to simplify the modules that use them. \*=========================================================================*/ #include "timeout.h" /* convenient shorthand */ typedef struct sockaddr SA; /*=========================================================================*\ * Functions bellow implement a comfortable platform independent * interface to sockets \*=========================================================================*/ #ifndef _WIN32 #pragma GCC visibility push(hidden) #endif int socket_waitfd(p_socket ps, int sw, p_timeout tm); int socket_open(void); int socket_close(void); void socket_destroy(p_socket ps); int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds, p_timeout tm); int socket_create(p_socket ps, int domain, int type, int protocol); int socket_bind(p_socket ps, SA *addr, socklen_t addr_len); int socket_listen(p_socket ps, int backlog); void socket_shutdown(p_socket ps, int how); int socket_connect(p_socket ps, SA *addr, socklen_t addr_len, p_timeout tm); int socket_accept(p_socket ps, p_socket pa, SA *addr, socklen_t *addr_len, p_timeout tm); int socket_send(p_socket ps, const char *data, size_t count, size_t *sent, p_timeout tm); int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent, SA *addr, socklen_t addr_len, p_timeout tm); int socket_recv(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm); int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got, SA *addr, socklen_t *addr_len, p_timeout tm); int socket_write(p_socket ps, const char *data, size_t count, size_t *sent, p_timeout tm); int socket_read(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm); void socket_setblocking(p_socket ps); void socket_setnonblocking(p_socket ps); int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp); int socket_gethostbyname(const char *addr, struct hostent **hp); const char *socket_hoststrerror(int err); const char *socket_strerror(int err); const char *socket_ioerror(p_socket ps, int err); const char *socket_gaistrerror(int err); #ifndef _WIN32 #pragma GCC visibility pop #endif #endif /* SOCKET_H */ ================================================ FILE: src/luasocket/socket.lua ================================================ ----------------------------------------------------------------------------- -- LuaSocket helper module -- Author: Diego Nehab ----------------------------------------------------------------------------- local _M = socket ----------------------------------------------------------------------------- -- Exported auxiliar functions ----------------------------------------------------------------------------- function _M.connect4(address, port, laddress, lport) return socket.connect(address, port, laddress, lport, "inet") end function _M.connect6(address, port, laddress, lport) return socket.connect(address, port, laddress, lport, "inet6") end function _M.bind(host, port, backlog) if host == "*" then host = "0.0.0.0" end local addrinfo, err = socket.dns.getaddrinfo(host); if not addrinfo then return nil, err end local sock, res err = "no info on address" for i, alt in ipairs(addrinfo) do if alt.family == "inet" then sock, err = socket.tcp4() else sock, err = socket.tcp6() end if not sock then return nil, err end sock:setoption("reuseaddr", true) res, err = sock:bind(alt.addr, port) if not res then sock:close() else res, err = sock:listen(backlog) if not res then sock:close() else return sock end end end return nil, err end _M.try = _M.newtry() function _M.choose(table) return function(name, opt1, opt2) if type(name) ~= "string" then name, opt1, opt2 = "default", name, opt1 end local f = table[name or "nil"] if not f then error("unknown key (".. tostring(name) ..")", 3) else return f(opt1, opt2) end end end ----------------------------------------------------------------------------- -- Socket sources and sinks, conforming to LTN12 ----------------------------------------------------------------------------- -- create namespaces inside LuaSocket namespace local sourcet, sinkt = {}, {} _M.sourcet = sourcet _M.sinkt = sinkt _M.BLOCKSIZE = 2048 sinkt["close-when-done"] = function(sock) return setmetatable({ getfd = function() return sock:getfd() end, dirty = function() return sock:dirty() end }, { __call = function(self, chunk, err) if not chunk then sock:close() return 1 else return sock:send(chunk) end end }) end sinkt["keep-open"] = function(sock) return setmetatable({ getfd = function() return sock:getfd() end, dirty = function() return sock:dirty() end }, { __call = function(self, chunk, err) if chunk then return sock:send(chunk) else return 1 end end }) end sinkt["default"] = sinkt["keep-open"] _M.sink = _M.choose(sinkt) sourcet["by-length"] = function(sock, length) return setmetatable({ getfd = function() return sock:getfd() end, dirty = function() return sock:dirty() end }, { __call = function() if length <= 0 then return nil end local size = math.min(socket.BLOCKSIZE, length) local chunk, err = sock:receive(size) if err then return nil, err end length = length - string.len(chunk) return chunk end }) end sourcet["until-closed"] = function(sock) local done return setmetatable({ getfd = function() return sock:getfd() end, dirty = function() return sock:dirty() end }, { __call = function() if done then return nil end local chunk, err, partial = sock:receive(socket.BLOCKSIZE) if not err then return chunk elseif err == "closed" then sock:close() done = 1 return partial else return nil, err end end }) end sourcet["default"] = sourcet["until-closed"] _M.source = _M.choose(sourcet) return _M ================================================ FILE: src/luasocket/tcp.c ================================================ /*=========================================================================*\ * TCP object * LuaSocket toolkit \*=========================================================================*/ #include "luasocket.h" #include "auxiliar.h" #include "socket.h" #include "inet.h" #include "options.h" #include "tcp.h" #include "../teliva.h" #include /*=========================================================================*\ * Internal function prototypes \*=========================================================================*/ static int global_create(lua_State *L); static int global_create4(lua_State *L); static int global_create6(lua_State *L); static int global_connect(lua_State *L); static int meth_connect(lua_State *L); static int meth_listen(lua_State *L); static int meth_getfamily(lua_State *L); static int meth_bind(lua_State *L); static int meth_send(lua_State *L); static int meth_getstats(lua_State *L); static int meth_setstats(lua_State *L); static int meth_getsockname(lua_State *L); static int meth_getpeername(lua_State *L); static int meth_shutdown(lua_State *L); static int meth_receive(lua_State *L); static int meth_accept(lua_State *L); static int meth_close(lua_State *L); static int meth_getoption(lua_State *L); static int meth_setoption(lua_State *L); static int meth_gettimeout(lua_State *L); static int meth_settimeout(lua_State *L); static int meth_getfd(lua_State *L); static int meth_setfd(lua_State *L); static int meth_dirty(lua_State *L); /* tcp object methods */ static luaL_Reg tcp_methods[] = { {"__gc", meth_close}, {"__tostring", auxiliar_tostring}, {"accept", meth_accept}, {"bind", meth_bind}, {"close", meth_close}, {"connect", meth_connect}, {"dirty", meth_dirty}, {"getfamily", meth_getfamily}, {"getfd", meth_getfd}, {"getoption", meth_getoption}, {"getpeername", meth_getpeername}, {"getsockname", meth_getsockname}, {"getstats", meth_getstats}, {"setstats", meth_setstats}, {"listen", meth_listen}, {"receive", meth_receive}, {"send", meth_send}, {"setfd", meth_setfd}, {"setoption", meth_setoption}, {"setpeername", meth_connect}, {"setsockname", meth_bind}, {"settimeout", meth_settimeout}, {"gettimeout", meth_gettimeout}, {"shutdown", meth_shutdown}, {NULL, NULL} }; /* socket option handlers */ static t_opt optget[] = { {"keepalive", opt_get_keepalive}, {"reuseaddr", opt_get_reuseaddr}, {"reuseport", opt_get_reuseport}, {"tcp-nodelay", opt_get_tcp_nodelay}, #ifdef TCP_KEEPIDLE {"tcp-keepidle", opt_get_tcp_keepidle}, #endif #ifdef TCP_KEEPCNT {"tcp-keepcnt", opt_get_tcp_keepcnt}, #endif #ifdef TCP_KEEPINTVL {"tcp-keepintvl", opt_get_tcp_keepintvl}, #endif {"linger", opt_get_linger}, {"error", opt_get_error}, {"recv-buffer-size", opt_get_recv_buf_size}, {"send-buffer-size", opt_get_send_buf_size}, {NULL, NULL} }; static t_opt optset[] = { {"keepalive", opt_set_keepalive}, {"reuseaddr", opt_set_reuseaddr}, {"reuseport", opt_set_reuseport}, {"tcp-nodelay", opt_set_tcp_nodelay}, #ifdef TCP_KEEPIDLE {"tcp-keepidle", opt_set_tcp_keepidle}, #endif #ifdef TCP_KEEPCNT {"tcp-keepcnt", opt_set_tcp_keepcnt}, #endif #ifdef TCP_KEEPINTVL {"tcp-keepintvl", opt_set_tcp_keepintvl}, #endif {"ipv6-v6only", opt_set_ip6_v6only}, {"linger", opt_set_linger}, {"recv-buffer-size", opt_set_recv_buf_size}, {"send-buffer-size", opt_set_send_buf_size}, {NULL, NULL} }; /* functions in library namespace */ static luaL_Reg func[] = { {"tcp", global_create}, {"tcp4", global_create4}, {"tcp6", global_create6}, {"connect", global_connect}, {NULL, NULL} }; /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ int tcp_open(lua_State *L) { /* create classes */ auxiliar_newclass(L, "tcp{master}", tcp_methods); auxiliar_newclass(L, "tcp{client}", tcp_methods); auxiliar_newclass(L, "tcp{server}", tcp_methods); /* create class groups */ auxiliar_add2group(L, "tcp{master}", "tcp{any}"); auxiliar_add2group(L, "tcp{client}", "tcp{any}"); auxiliar_add2group(L, "tcp{server}", "tcp{any}"); /* define library functions */ luaL_setfuncs(L, func, 0); return 0; } /*=========================================================================*\ * Lua methods \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Just call buffered IO methods \*-------------------------------------------------------------------------*/ static int meth_send(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); return buffer_meth_send(L, &tcp->buf); } static int meth_receive(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); return buffer_meth_receive(L, &tcp->buf); } static int meth_getstats(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); return buffer_meth_getstats(L, &tcp->buf); } static int meth_setstats(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); return buffer_meth_setstats(L, &tcp->buf); } /*-------------------------------------------------------------------------*\ * Just call option handler \*-------------------------------------------------------------------------*/ static int meth_getoption(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); return opt_meth_getoption(L, optget, &tcp->sock); } static int meth_setoption(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); return opt_meth_setoption(L, optset, &tcp->sock); } /*-------------------------------------------------------------------------*\ * Select support methods \*-------------------------------------------------------------------------*/ static int meth_getfd(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); lua_pushnumber(L, (int) tcp->sock); return 1; } /* this is very dangerous, but can be handy for those that are brave enough */ static int meth_setfd(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); tcp->sock = (t_socket) luaL_checknumber(L, 2); return 0; } static int meth_dirty(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); lua_pushboolean(L, !buffer_isempty(&tcp->buf)); return 1; } /*-------------------------------------------------------------------------*\ * Waits for and returns a client object attempting connection to the * server object \*-------------------------------------------------------------------------*/ static int meth_accept(lua_State *L) { p_tcp server = (p_tcp) auxiliar_checkclass(L, "tcp{server}", 1); p_timeout tm = timeout_markstart(&server->tm); t_socket sock; append_to_audit_log(L, "socket.accept()"); const char *err = inet_tryaccept(&server->sock, server->family, &sock, tm); /* if successful, push client socket */ if (err == NULL) { p_tcp clnt = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); auxiliar_setclass(L, "tcp{client}", -1); /* initialize structure fields */ memset(clnt, 0, sizeof(t_tcp)); socket_setnonblocking(&sock); clnt->sock = sock; io_init(&clnt->io, (p_send) socket_send, (p_recv) socket_recv, (p_error) socket_ioerror, &clnt->sock); timeout_init(&clnt->tm, -1, -1); buffer_init(&clnt->buf, &clnt->io, &clnt->tm); clnt->family = server->family; return 1; } else { lua_pushnil(L); lua_pushstring(L, err); return 2; } } /*-------------------------------------------------------------------------*\ * Binds an object to an address \*-------------------------------------------------------------------------*/ static int meth_bind(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{master}", 1); const char *address = luaL_checkstring(L, 2); const char *port = luaL_checkstring(L, 3); static char buffer[1024] = {0}; memset(buffer, '\0', 1024); snprintf(buffer, 1020, "socket.bind(\"%s\", %s)", address, port); append_to_audit_log(L, buffer); const char *err; struct addrinfo bindhints; memset(&bindhints, 0, sizeof(bindhints)); bindhints.ai_socktype = SOCK_STREAM; bindhints.ai_family = tcp->family; bindhints.ai_flags = AI_PASSIVE; err = inet_trybind(&tcp->sock, &tcp->family, address, port, &bindhints); if (err) { lua_pushnil(L); lua_pushstring(L, err); return 2; } lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Turns a master tcp object into a client object. \*-------------------------------------------------------------------------*/ static int meth_connect(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); const char *address = luaL_checkstring(L, 2); const char *port = luaL_checkstring(L, 3); struct addrinfo connecthints; const char *err; static char buffer[1024] = {0}; memset(buffer, '\0', 1024); snprintf(buffer, 1020, "socket.connect(\"%s\", %s)", address, port); append_to_audit_log(L, buffer); memset(&connecthints, 0, sizeof(connecthints)); connecthints.ai_socktype = SOCK_STREAM; /* make sure we try to connect only to the same family */ connecthints.ai_family = tcp->family; timeout_markstart(&tcp->tm); err = inet_tryconnect(&tcp->sock, &tcp->family, address, port, &tcp->tm, &connecthints); /* have to set the class even if it failed due to non-blocking connects */ auxiliar_setclass(L, "tcp{client}", 1); if (err) { lua_pushnil(L); lua_pushstring(L, err); return 2; } lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Closes socket used by object \*-------------------------------------------------------------------------*/ static int meth_close(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); socket_destroy(&tcp->sock); lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Returns family as string \*-------------------------------------------------------------------------*/ static int meth_getfamily(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); if (tcp->family == AF_INET6) { lua_pushliteral(L, "inet6"); return 1; } else if (tcp->family == AF_INET) { lua_pushliteral(L, "inet4"); return 1; } else { lua_pushliteral(L, "inet4"); return 1; } } /*-------------------------------------------------------------------------*\ * Puts the sockt in listen mode \*-------------------------------------------------------------------------*/ static int meth_listen(lua_State *L) { append_to_audit_log(L, "socket.listen()"); p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{master}", 1); int backlog = (int) luaL_optnumber(L, 2, 32); int err = socket_listen(&tcp->sock, backlog); if (err != IO_DONE) { lua_pushnil(L); lua_pushstring(L, socket_strerror(err)); return 2; } /* turn master object into a server object */ auxiliar_setclass(L, "tcp{server}", 1); lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Shuts the connection down partially \*-------------------------------------------------------------------------*/ static int meth_shutdown(lua_State *L) { /* SHUT_RD, SHUT_WR, SHUT_RDWR have the value 0, 1, 2, so we can use method index directly */ static const char* methods[] = { "receive", "send", "both", NULL }; p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); int how = luaL_checkoption(L, 2, "both", methods); socket_shutdown(&tcp->sock, how); lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Just call inet methods \*-------------------------------------------------------------------------*/ static int meth_getpeername(lua_State *L) { append_to_audit_log(L, "socket.getpeername()"); p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); return inet_meth_getpeername(L, &tcp->sock, tcp->family); } static int meth_getsockname(lua_State *L) { append_to_audit_log(L, "socket.getsockname()"); p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); return inet_meth_getsockname(L, &tcp->sock, tcp->family); } /*-------------------------------------------------------------------------*\ * Just call tm methods \*-------------------------------------------------------------------------*/ static int meth_settimeout(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); return timeout_meth_settimeout(L, &tcp->tm); } static int meth_gettimeout(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); return timeout_meth_gettimeout(L, &tcp->tm); } /*=========================================================================*\ * Library functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Creates a master tcp object \*-------------------------------------------------------------------------*/ static int tcp_create(lua_State *L, int family) { p_tcp tcp = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); memset(tcp, 0, sizeof(t_tcp)); /* set its type as master object */ auxiliar_setclass(L, "tcp{master}", -1); /* if family is AF_UNSPEC, we leave the socket invalid and * store AF_UNSPEC into family. This will allow it to later be * replaced with an AF_INET6 or AF_INET socket upon first use. */ tcp->sock = SOCKET_INVALID; tcp->family = family; io_init(&tcp->io, (p_send) socket_send, (p_recv) socket_recv, (p_error) socket_ioerror, &tcp->sock); timeout_init(&tcp->tm, -1, -1); buffer_init(&tcp->buf, &tcp->io, &tcp->tm); if (family != AF_UNSPEC) { const char *err = inet_trycreate(&tcp->sock, family, SOCK_STREAM, 0); if (err != NULL) { lua_pushnil(L); lua_pushstring(L, err); return 2; } socket_setnonblocking(&tcp->sock); } return 1; } static int global_create(lua_State *L) { return tcp_create(L, AF_UNSPEC); } static int global_create4(lua_State *L) { return tcp_create(L, AF_INET); } static int global_create6(lua_State *L) { return tcp_create(L, AF_INET6); } static int global_connect(lua_State *L) { const char *remoteaddr = luaL_checkstring(L, 1); const char *remoteserv = luaL_checkstring(L, 2); const char *localaddr = luaL_optstring(L, 3, NULL); const char *localserv = luaL_optstring(L, 4, "0"); int family = inet_optfamily(L, 5, "unspec"); p_tcp tcp = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); struct addrinfo bindhints, connecthints; const char *err = NULL; /* initialize tcp structure */ memset(tcp, 0, sizeof(t_tcp)); io_init(&tcp->io, (p_send) socket_send, (p_recv) socket_recv, (p_error) socket_ioerror, &tcp->sock); timeout_init(&tcp->tm, -1, -1); buffer_init(&tcp->buf, &tcp->io, &tcp->tm); tcp->sock = SOCKET_INVALID; tcp->family = AF_UNSPEC; /* allow user to pick local address and port */ memset(&bindhints, 0, sizeof(bindhints)); bindhints.ai_socktype = SOCK_STREAM; bindhints.ai_family = family; bindhints.ai_flags = AI_PASSIVE; if (localaddr) { err = inet_trybind(&tcp->sock, &tcp->family, localaddr, localserv, &bindhints); if (err) { lua_pushnil(L); lua_pushstring(L, err); return 2; } } /* try to connect to remote address and port */ memset(&connecthints, 0, sizeof(connecthints)); connecthints.ai_socktype = SOCK_STREAM; /* make sure we try to connect only to the same family */ connecthints.ai_family = tcp->family; err = inet_tryconnect(&tcp->sock, &tcp->family, remoteaddr, remoteserv, &tcp->tm, &connecthints); if (err) { socket_destroy(&tcp->sock); lua_pushnil(L); lua_pushstring(L, err); return 2; } auxiliar_setclass(L, "tcp{client}", -1); return 1; } ================================================ FILE: src/luasocket/tcp.h ================================================ #ifndef TCP_H #define TCP_H /*=========================================================================*\ * TCP object * LuaSocket toolkit * * The tcp.h module is basicly a glue that puts together modules buffer.h, * timeout.h socket.h and inet.h to provide the LuaSocket TCP (AF_INET, * SOCK_STREAM) support. * * Three classes are defined: master, client and server. The master class is * a newly created tcp object, that has not been bound or connected. Server * objects are tcp objects bound to some local address. Client objects are * tcp objects either connected to some address or returned by the accept * method of a server object. \*=========================================================================*/ #include "luasocket.h" #include "buffer.h" #include "timeout.h" #include "socket.h" typedef struct t_tcp_ { t_socket sock; t_io io; t_buffer buf; t_timeout tm; int family; } t_tcp; typedef t_tcp *p_tcp; #ifndef _WIN32 #pragma GCC visibility push(hidden) #endif int tcp_open(lua_State *L); #ifndef _WIN32 #pragma GCC visibility pop #endif #endif /* TCP_H */ ================================================ FILE: src/luasocket/timeout.c ================================================ /*=========================================================================*\ * Timeout management functions * LuaSocket toolkit \*=========================================================================*/ #include "luasocket.h" #include "auxiliar.h" #include "timeout.h" #include #include #include #ifdef _WIN32 #include #else #include #include #endif /* min and max macros */ #ifndef MIN #define MIN(x, y) ((x) < (y) ? x : y) #endif #ifndef MAX #define MAX(x, y) ((x) > (y) ? x : y) #endif /*=========================================================================*\ * Internal function prototypes \*=========================================================================*/ static int timeout_lua_gettime(lua_State *L); static int timeout_lua_sleep(lua_State *L); static luaL_Reg func[] = { { "gettime", timeout_lua_gettime }, { "sleep", timeout_lua_sleep }, { NULL, NULL } }; /*=========================================================================*\ * Exported functions. \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Initialize structure \*-------------------------------------------------------------------------*/ void timeout_init(p_timeout tm, double block, double total) { tm->block = block; tm->total = total; } /*-------------------------------------------------------------------------*\ * Determines how much time we have left for the next system call, * if the previous call was successful * Input * tm: timeout control structure * Returns * the number of ms left or -1 if there is no time limit \*-------------------------------------------------------------------------*/ double timeout_get(p_timeout tm) { if (tm->block < 0.0 && tm->total < 0.0) { return -1; } else if (tm->block < 0.0) { double t = tm->total - timeout_gettime() + tm->start; return MAX(t, 0.0); } else if (tm->total < 0.0) { return tm->block; } else { double t = tm->total - timeout_gettime() + tm->start; return MIN(tm->block, MAX(t, 0.0)); } } /*-------------------------------------------------------------------------*\ * Returns time since start of operation * Input * tm: timeout control structure * Returns * start field of structure \*-------------------------------------------------------------------------*/ double timeout_getstart(p_timeout tm) { return tm->start; } /*-------------------------------------------------------------------------*\ * Determines how much time we have left for the next system call, * if the previous call was a failure * Input * tm: timeout control structure * Returns * the number of ms left or -1 if there is no time limit \*-------------------------------------------------------------------------*/ double timeout_getretry(p_timeout tm) { if (tm->block < 0.0 && tm->total < 0.0) { return -1; } else if (tm->block < 0.0) { double t = tm->total - timeout_gettime() + tm->start; return MAX(t, 0.0); } else if (tm->total < 0.0) { double t = tm->block - timeout_gettime() + tm->start; return MAX(t, 0.0); } else { double t = tm->total - timeout_gettime() + tm->start; return MIN(tm->block, MAX(t, 0.0)); } } /*-------------------------------------------------------------------------*\ * Marks the operation start time in structure * Input * tm: timeout control structure \*-------------------------------------------------------------------------*/ p_timeout timeout_markstart(p_timeout tm) { tm->start = timeout_gettime(); return tm; } /*-------------------------------------------------------------------------*\ * Gets time in s, relative to January 1, 1970 (UTC) * Returns * time in s. \*-------------------------------------------------------------------------*/ #ifdef _WIN32 double timeout_gettime(void) { FILETIME ft; double t; GetSystemTimeAsFileTime(&ft); /* Windows file time (time since January 1, 1601 (UTC)) */ t = ft.dwLowDateTime/1.0e7 + ft.dwHighDateTime*(4294967296.0/1.0e7); /* convert to Unix Epoch time (time since January 1, 1970 (UTC)) */ return (t - 11644473600.0); } #else double timeout_gettime(void) { struct timeval v; gettimeofday(&v, (struct timezone *) NULL); /* Unix Epoch time (time since January 1, 1970 (UTC)) */ return v.tv_sec + v.tv_usec/1.0e6; } #endif /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ int timeout_open(lua_State *L) { luaL_setfuncs(L, func, 0); return 0; } /*-------------------------------------------------------------------------*\ * Sets timeout values for IO operations * Lua Input: base, time [, mode] * time: time out value in seconds * mode: "b" for block timeout, "t" for total timeout. (default: b) \*-------------------------------------------------------------------------*/ int timeout_meth_settimeout(lua_State *L, p_timeout tm) { double t = luaL_optnumber(L, 2, -1); const char *mode = luaL_optstring(L, 3, "b"); switch (*mode) { case 'b': tm->block = t; break; case 'r': case 't': tm->total = t; break; default: luaL_argcheck(L, 0, 3, "invalid timeout mode"); break; } lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Gets timeout values for IO operations * Lua Output: block, total \*-------------------------------------------------------------------------*/ int timeout_meth_gettimeout(lua_State *L, p_timeout tm) { lua_pushnumber(L, tm->block); lua_pushnumber(L, tm->total); return 2; } /*=========================================================================*\ * Test support functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Returns the time the system has been up, in secconds. \*-------------------------------------------------------------------------*/ static int timeout_lua_gettime(lua_State *L) { lua_pushnumber(L, timeout_gettime()); return 1; } /*-------------------------------------------------------------------------*\ * Sleep for n seconds. \*-------------------------------------------------------------------------*/ #ifdef _WIN32 int timeout_lua_sleep(lua_State *L) { double n = luaL_checknumber(L, 1); if (n < 0.0) n = 0.0; if (n < DBL_MAX/1000.0) n *= 1000.0; if (n > INT_MAX) n = INT_MAX; Sleep((int)n); return 0; } #else int timeout_lua_sleep(lua_State *L) { double n = luaL_checknumber(L, 1); struct timespec t, r; if (n < 0.0) n = 0.0; if (n > INT_MAX) n = INT_MAX; t.tv_sec = (int) n; n -= t.tv_sec; t.tv_nsec = (int) (n * 1000000000); if (t.tv_nsec >= 1000000000) t.tv_nsec = 999999999; while (nanosleep(&t, &r) != 0) { t.tv_sec = r.tv_sec; t.tv_nsec = r.tv_nsec; } return 0; } #endif ================================================ FILE: src/luasocket/timeout.h ================================================ #ifndef TIMEOUT_H #define TIMEOUT_H /*=========================================================================*\ * Timeout management functions * LuaSocket toolkit \*=========================================================================*/ #include "luasocket.h" /* timeout control structure */ typedef struct t_timeout_ { double block; /* maximum time for blocking calls */ double total; /* total number of miliseconds for operation */ double start; /* time of start of operation */ } t_timeout; typedef t_timeout *p_timeout; #ifndef _WIN32 #pragma GCC visibility push(hidden) #endif void timeout_init(p_timeout tm, double block, double total); double timeout_get(p_timeout tm); double timeout_getstart(p_timeout tm); double timeout_getretry(p_timeout tm); p_timeout timeout_markstart(p_timeout tm); double timeout_gettime(void); int timeout_open(lua_State *L); int timeout_meth_settimeout(lua_State *L, p_timeout tm); int timeout_meth_gettimeout(lua_State *L, p_timeout tm); #ifndef _WIN32 #pragma GCC visibility pop #endif #define timeout_iszero(tm) ((tm)->block == 0.0) #endif /* TIMEOUT_H */ ================================================ FILE: src/luasocket/tp.lua ================================================ ----------------------------------------------------------------------------- -- Unified SMTP/FTP subsystem -- LuaSocket toolkit. -- Author: Diego Nehab ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- -- Declare module and import dependencies ----------------------------------------------------------------------------- socket.tp = {} local _M = socket.tp ----------------------------------------------------------------------------- -- Program constants ----------------------------------------------------------------------------- _M.TIMEOUT = 60 ----------------------------------------------------------------------------- -- Implementation ----------------------------------------------------------------------------- -- gets server reply (works for SMTP and FTP) local function get_reply(c) local code, current, sep local line, err = c:receive() local reply = line if err then return nil, err end code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) if not code then return nil, "invalid server reply" end if sep == "-" then -- reply is multiline repeat line, err = c:receive() if err then return nil, err end current, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) reply = reply .. "\n" .. line -- reply ends with same code until code == current and sep == " " end return code, reply end -- metatable for sock object local metat = { __index = {} } function metat.__index:getpeername() return self.c:getpeername() end function metat.__index:getsockname() return self.c:getpeername() end function metat.__index:check(ok) local code, reply = get_reply(self.c) if not code then return nil, reply end if type(ok) ~= "function" then if type(ok) == "table" then for i, v in ipairs(ok) do if string.find(code, v) then return tonumber(code), reply end end return nil, reply else if string.find(code, ok) then return tonumber(code), reply else return nil, reply end end else return ok(tonumber(code), reply) end end function metat.__index:command(cmd, arg) cmd = string.upper(cmd) if arg then return self.c:send(cmd .. " " .. arg.. "\r\n") else return self.c:send(cmd .. "\r\n") end end function metat.__index:sink(snk, pat) local chunk, err = self.c:receive(pat) return snk(chunk, err) end function metat.__index:send(data) return self.c:send(data) end function metat.__index:receive(pat) return self.c:receive(pat) end function metat.__index:getfd() return self.c:getfd() end function metat.__index:dirty() return self.c:dirty() end function metat.__index:getcontrol() return self.c end function metat.__index:source(source, step) local sink = socket.sink("keep-open", self.c) local ret, err = ltn12.pump.all(source, sink, step or ltn12.pump.step) return ret, err end -- closes the underlying c function metat.__index:close() self.c:close() return 1 end -- connect with server and return c object function _M.connect(host, port, timeout, create) local c, e = (create or socket.tcp)() if not c then return nil, e end c:settimeout(timeout or _M.TIMEOUT) local r, e = c:connect(host, port) if not r then c:close() return nil, e end return setmetatable({c = c}, metat) end return _M ================================================ FILE: src/luasocket/udp.c ================================================ /*=========================================================================*\ * UDP object * LuaSocket toolkit \*=========================================================================*/ #include "luasocket.h" #include "auxiliar.h" #include "socket.h" #include "inet.h" #include "options.h" #include "udp.h" #include #include /* min and max macros */ #ifndef MIN #define MIN(x, y) ((x) < (y) ? x : y) #endif #ifndef MAX #define MAX(x, y) ((x) > (y) ? x : y) #endif /*=========================================================================*\ * Internal function prototypes \*=========================================================================*/ static int global_create(lua_State *L); static int global_create4(lua_State *L); static int global_create6(lua_State *L); static int meth_send(lua_State *L); static int meth_sendto(lua_State *L); static int meth_receive(lua_State *L); static int meth_receivefrom(lua_State *L); static int meth_getfamily(lua_State *L); static int meth_getsockname(lua_State *L); static int meth_getpeername(lua_State *L); static int meth_gettimeout(lua_State *L); static int meth_setsockname(lua_State *L); static int meth_setpeername(lua_State *L); static int meth_close(lua_State *L); static int meth_setoption(lua_State *L); static int meth_getoption(lua_State *L); static int meth_settimeout(lua_State *L); static int meth_getfd(lua_State *L); static int meth_setfd(lua_State *L); static int meth_dirty(lua_State *L); /* udp object methods */ static luaL_Reg udp_methods[] = { {"__gc", meth_close}, {"__tostring", auxiliar_tostring}, {"close", meth_close}, {"dirty", meth_dirty}, {"getfamily", meth_getfamily}, {"getfd", meth_getfd}, {"getpeername", meth_getpeername}, {"getsockname", meth_getsockname}, {"receive", meth_receive}, {"receivefrom", meth_receivefrom}, {"send", meth_send}, {"sendto", meth_sendto}, {"setfd", meth_setfd}, {"setoption", meth_setoption}, {"getoption", meth_getoption}, {"setpeername", meth_setpeername}, {"setsockname", meth_setsockname}, {"settimeout", meth_settimeout}, {"gettimeout", meth_gettimeout}, {NULL, NULL} }; /* socket options for setoption */ static t_opt optset[] = { {"dontroute", opt_set_dontroute}, {"broadcast", opt_set_broadcast}, {"reuseaddr", opt_set_reuseaddr}, {"reuseport", opt_set_reuseport}, {"ip-multicast-if", opt_set_ip_multicast_if}, {"ip-multicast-ttl", opt_set_ip_multicast_ttl}, {"ip-multicast-loop", opt_set_ip_multicast_loop}, {"ip-add-membership", opt_set_ip_add_membership}, {"ip-drop-membership", opt_set_ip_drop_membersip}, {"ipv6-unicast-hops", opt_set_ip6_unicast_hops}, {"ipv6-multicast-hops", opt_set_ip6_unicast_hops}, {"ipv6-multicast-loop", opt_set_ip6_multicast_loop}, {"ipv6-add-membership", opt_set_ip6_add_membership}, {"ipv6-drop-membership", opt_set_ip6_drop_membersip}, {"ipv6-v6only", opt_set_ip6_v6only}, {"recv-buffer-size", opt_set_recv_buf_size}, {"send-buffer-size", opt_set_send_buf_size}, {NULL, NULL} }; /* socket options for getoption */ static t_opt optget[] = { {"dontroute", opt_get_dontroute}, {"broadcast", opt_get_broadcast}, {"reuseaddr", opt_get_reuseaddr}, {"reuseport", opt_get_reuseport}, {"ip-multicast-if", opt_get_ip_multicast_if}, {"ip-multicast-loop", opt_get_ip_multicast_loop}, {"error", opt_get_error}, {"ipv6-unicast-hops", opt_get_ip6_unicast_hops}, {"ipv6-multicast-hops", opt_get_ip6_unicast_hops}, {"ipv6-multicast-loop", opt_get_ip6_multicast_loop}, {"ipv6-v6only", opt_get_ip6_v6only}, {"recv-buffer-size", opt_get_recv_buf_size}, {"send-buffer-size", opt_get_send_buf_size}, {NULL, NULL} }; /* functions in library namespace */ static luaL_Reg func[] = { {"udp", global_create}, {"udp4", global_create4}, {"udp6", global_create6}, {NULL, NULL} }; /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ int udp_open(lua_State *L) { /* create classes */ auxiliar_newclass(L, "udp{connected}", udp_methods); auxiliar_newclass(L, "udp{unconnected}", udp_methods); /* create class groups */ auxiliar_add2group(L, "udp{connected}", "udp{any}"); auxiliar_add2group(L, "udp{unconnected}", "udp{any}"); auxiliar_add2group(L, "udp{connected}", "select{able}"); auxiliar_add2group(L, "udp{unconnected}", "select{able}"); /* define library functions */ luaL_setfuncs(L, func, 0); /* export default UDP size */ lua_pushliteral(L, "_DATAGRAMSIZE"); lua_pushinteger(L, UDP_DATAGRAMSIZE); lua_rawset(L, -3); return 0; } /*=========================================================================*\ * Lua methods \*=========================================================================*/ static const char *udp_strerror(int err) { /* a 'closed' error on an unconnected means the target address was not * accepted by the transport layer */ if (err == IO_CLOSED) return "refused"; else return socket_strerror(err); } /*-------------------------------------------------------------------------*\ * Send data through connected udp socket \*-------------------------------------------------------------------------*/ static int meth_send(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{connected}", 1); p_timeout tm = &udp->tm; size_t count, sent = 0; int err; const char *data = luaL_checklstring(L, 2, &count); timeout_markstart(tm); err = socket_send(&udp->sock, data, count, &sent, tm); if (err != IO_DONE) { lua_pushnil(L); lua_pushstring(L, udp_strerror(err)); return 2; } lua_pushnumber(L, (lua_Number) sent); return 1; } /*-------------------------------------------------------------------------*\ * Send data through unconnected udp socket \*-------------------------------------------------------------------------*/ static int meth_sendto(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{unconnected}", 1); size_t count, sent = 0; const char *data = luaL_checklstring(L, 2, &count); const char *ip = luaL_checkstring(L, 3); const char *port = luaL_checkstring(L, 4); p_timeout tm = &udp->tm; int err; struct addrinfo aihint; struct addrinfo *ai; memset(&aihint, 0, sizeof(aihint)); aihint.ai_family = udp->family; aihint.ai_socktype = SOCK_DGRAM; aihint.ai_flags = AI_NUMERICHOST; #ifdef AI_NUMERICSERV aihint.ai_flags |= AI_NUMERICSERV; #endif err = getaddrinfo(ip, port, &aihint, &ai); if (err) { lua_pushnil(L); lua_pushstring(L, gai_strerror(err)); return 2; } /* create socket if on first sendto if AF_UNSPEC was set */ if (udp->family == AF_UNSPEC && udp->sock == SOCKET_INVALID) { struct addrinfo *ap; const char *errstr = NULL; for (ap = ai; ap != NULL; ap = ap->ai_next) { errstr = inet_trycreate(&udp->sock, ap->ai_family, SOCK_DGRAM, 0); if (errstr == NULL) { socket_setnonblocking(&udp->sock); udp->family = ap->ai_family; break; } } if (errstr != NULL) { lua_pushnil(L); lua_pushstring(L, errstr); freeaddrinfo(ai); return 2; } } timeout_markstart(tm); err = socket_sendto(&udp->sock, data, count, &sent, ai->ai_addr, (socklen_t) ai->ai_addrlen, tm); freeaddrinfo(ai); if (err != IO_DONE) { lua_pushnil(L); lua_pushstring(L, udp_strerror(err)); return 2; } lua_pushnumber(L, (lua_Number) sent); return 1; } /*-------------------------------------------------------------------------*\ * Receives data from a UDP socket \*-------------------------------------------------------------------------*/ static int meth_receive(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); char buf[UDP_DATAGRAMSIZE]; size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; int err; p_timeout tm = &udp->tm; timeout_markstart(tm); if (!dgram) { lua_pushnil(L); lua_pushliteral(L, "out of memory"); return 2; } err = socket_recv(&udp->sock, dgram, wanted, &got, tm); /* Unlike TCP, recv() of zero is not closed, but a zero-length packet. */ if (err != IO_DONE && err != IO_CLOSED) { lua_pushnil(L); lua_pushstring(L, udp_strerror(err)); if (wanted > sizeof(buf)) free(dgram); return 2; } lua_pushlstring(L, dgram, got); if (wanted > sizeof(buf)) free(dgram); return 1; } /*-------------------------------------------------------------------------*\ * Receives data and sender from a UDP socket \*-------------------------------------------------------------------------*/ static int meth_receivefrom(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{unconnected}", 1); char buf[UDP_DATAGRAMSIZE]; size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); char addrstr[INET6_ADDRSTRLEN]; char portstr[6]; int err; p_timeout tm = &udp->tm; timeout_markstart(tm); if (!dgram) { lua_pushnil(L); lua_pushliteral(L, "out of memory"); return 2; } err = socket_recvfrom(&udp->sock, dgram, wanted, &got, (SA *) &addr, &addr_len, tm); /* Unlike TCP, recv() of zero is not closed, but a zero-length packet. */ if (err != IO_DONE && err != IO_CLOSED) { lua_pushnil(L); lua_pushstring(L, udp_strerror(err)); if (wanted > sizeof(buf)) free(dgram); return 2; } err = getnameinfo((struct sockaddr *)&addr, addr_len, addrstr, INET6_ADDRSTRLEN, portstr, 6, NI_NUMERICHOST | NI_NUMERICSERV); if (err) { lua_pushnil(L); lua_pushstring(L, gai_strerror(err)); if (wanted > sizeof(buf)) free(dgram); return 2; } lua_pushlstring(L, dgram, got); lua_pushstring(L, addrstr); lua_pushinteger(L, (int) strtol(portstr, (char **) NULL, 10)); if (wanted > sizeof(buf)) free(dgram); return 3; } /*-------------------------------------------------------------------------*\ * Returns family as string \*-------------------------------------------------------------------------*/ static int meth_getfamily(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); if (udp->family == AF_INET6) { lua_pushliteral(L, "inet6"); return 1; } else { lua_pushliteral(L, "inet4"); return 1; } } /*-------------------------------------------------------------------------*\ * Select support methods \*-------------------------------------------------------------------------*/ static int meth_getfd(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); lua_pushnumber(L, (int) udp->sock); return 1; } /* this is very dangerous, but can be handy for those that are brave enough */ static int meth_setfd(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); udp->sock = (t_socket) luaL_checknumber(L, 2); return 0; } static int meth_dirty(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); (void) udp; lua_pushboolean(L, 0); return 1; } /*-------------------------------------------------------------------------*\ * Just call inet methods \*-------------------------------------------------------------------------*/ static int meth_getpeername(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{connected}", 1); return inet_meth_getpeername(L, &udp->sock, udp->family); } static int meth_getsockname(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); return inet_meth_getsockname(L, &udp->sock, udp->family); } /*-------------------------------------------------------------------------*\ * Just call option handler \*-------------------------------------------------------------------------*/ static int meth_setoption(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); return opt_meth_setoption(L, optset, &udp->sock); } /*-------------------------------------------------------------------------*\ * Just call option handler \*-------------------------------------------------------------------------*/ static int meth_getoption(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); return opt_meth_getoption(L, optget, &udp->sock); } /*-------------------------------------------------------------------------*\ * Just call tm methods \*-------------------------------------------------------------------------*/ static int meth_settimeout(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); return timeout_meth_settimeout(L, &udp->tm); } static int meth_gettimeout(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); return timeout_meth_gettimeout(L, &udp->tm); } /*-------------------------------------------------------------------------*\ * Turns a master udp object into a client object. \*-------------------------------------------------------------------------*/ static int meth_setpeername(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); p_timeout tm = &udp->tm; const char *address = luaL_checkstring(L, 2); int connecting = strcmp(address, "*"); const char *port = connecting? luaL_checkstring(L, 3): "0"; struct addrinfo connecthints; const char *err; memset(&connecthints, 0, sizeof(connecthints)); connecthints.ai_socktype = SOCK_DGRAM; /* make sure we try to connect only to the same family */ connecthints.ai_family = udp->family; if (connecting) { err = inet_tryconnect(&udp->sock, &udp->family, address, port, tm, &connecthints); if (err) { lua_pushnil(L); lua_pushstring(L, err); return 2; } auxiliar_setclass(L, "udp{connected}", 1); } else { /* we ignore possible errors because Mac OS X always * returns EAFNOSUPPORT */ inet_trydisconnect(&udp->sock, udp->family, tm); auxiliar_setclass(L, "udp{unconnected}", 1); } lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Closes socket used by object \*-------------------------------------------------------------------------*/ static int meth_close(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); socket_destroy(&udp->sock); lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Turns a master object into a server object \*-------------------------------------------------------------------------*/ static int meth_setsockname(lua_State *L) { p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{unconnected}", 1); const char *address = luaL_checkstring(L, 2); const char *port = luaL_checkstring(L, 3); const char *err; struct addrinfo bindhints; memset(&bindhints, 0, sizeof(bindhints)); bindhints.ai_socktype = SOCK_DGRAM; bindhints.ai_family = udp->family; bindhints.ai_flags = AI_PASSIVE; err = inet_trybind(&udp->sock, &udp->family, address, port, &bindhints); if (err) { lua_pushnil(L); lua_pushstring(L, err); return 2; } lua_pushnumber(L, 1); return 1; } /*=========================================================================*\ * Library functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Creates a master udp object \*-------------------------------------------------------------------------*/ static int udp_create(lua_State *L, int family) { /* allocate udp object */ p_udp udp = (p_udp) lua_newuserdata(L, sizeof(t_udp)); auxiliar_setclass(L, "udp{unconnected}", -1); /* if family is AF_UNSPEC, we leave the socket invalid and * store AF_UNSPEC into family. This will allow it to later be * replaced with an AF_INET6 or AF_INET socket upon first use. */ udp->sock = SOCKET_INVALID; timeout_init(&udp->tm, -1, -1); udp->family = family; if (family != AF_UNSPEC) { const char *err = inet_trycreate(&udp->sock, family, SOCK_DGRAM, 0); if (err != NULL) { lua_pushnil(L); lua_pushstring(L, err); return 2; } socket_setnonblocking(&udp->sock); } return 1; } static int global_create(lua_State *L) { return udp_create(L, AF_UNSPEC); } static int global_create4(lua_State *L) { return udp_create(L, AF_INET); } static int global_create6(lua_State *L) { return udp_create(L, AF_INET6); } ================================================ FILE: src/luasocket/udp.h ================================================ #ifndef UDP_H #define UDP_H /*=========================================================================*\ * UDP object * LuaSocket toolkit * * The udp.h module provides LuaSocket with support for UDP protocol * (AF_INET, SOCK_DGRAM). * * Two classes are defined: connected and unconnected. UDP objects are * originally unconnected. They can be "connected" to a given address * with a call to the setpeername function. The same function can be used to * break the connection. \*=========================================================================*/ #include "luasocket.h" #include "timeout.h" #include "socket.h" #define UDP_DATAGRAMSIZE 8192 typedef struct t_udp_ { t_socket sock; t_timeout tm; int family; } t_udp; typedef t_udp *p_udp; #ifndef _WIN32 #pragma GCC visibility push(hidden) #endif int udp_open(lua_State *L); #ifndef _WIN32 #pragma GCC visibility pop #endif #endif /* UDP_H */ ================================================ FILE: src/luasocket/unix.c ================================================ /*=========================================================================*\ * Unix domain socket * LuaSocket toolkit \*=========================================================================*/ #include "luasocket.h" #include "unixstream.h" #include "unixdgram.h" /*-------------------------------------------------------------------------*\ * Modules and functions \*-------------------------------------------------------------------------*/ static const luaL_Reg mod[] = { {"stream", unixstream_open}, {"dgram", unixdgram_open}, {NULL, NULL} }; static void add_alias(lua_State *L, int index, const char *name, const char *target) { lua_getfield(L, index, target); lua_setfield(L, index, name); } static int compat_socket_unix_call(lua_State *L) { /* Look up socket.unix.stream in the socket.unix table (which is the first * argument). */ lua_getfield(L, 1, "stream"); /* Replace the stack entry for the socket.unix table with the * socket.unix.stream function. */ lua_replace(L, 1); /* Call socket.unix.stream, passing along any arguments. */ int n = lua_gettop(L); lua_call(L, n-1, LUA_MULTRET); /* Pass along the return values from socket.unix.stream. */ n = lua_gettop(L); return n; } /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ LUASOCKET_API int luaopen_socket_unix(lua_State *L) { int i; lua_newtable(L); int socket_unix_table = lua_gettop(L); for (i = 0; mod[i].name; i++) mod[i].func(L); /* Add backwards compatibility aliases "tcp" and "udp" for the "stream" and * "dgram" functions. */ add_alias(L, socket_unix_table, "tcp", "stream"); add_alias(L, socket_unix_table, "udp", "dgram"); /* Add a backwards compatibility function and a metatable setup to call it * for the old socket.unix() interface. */ lua_pushcfunction(L, compat_socket_unix_call); lua_setfield(L, socket_unix_table, "__call"); lua_pushvalue(L, socket_unix_table); lua_setmetatable(L, socket_unix_table); return 1; } ================================================ FILE: src/luasocket/unix.h ================================================ #ifndef UNIX_H #define UNIX_H /*=========================================================================*\ * Unix domain object * LuaSocket toolkit * * This module is just an example of how to extend LuaSocket with a new * domain. \*=========================================================================*/ #include "luasocket.h" #include "buffer.h" #include "timeout.h" #include "socket.h" typedef struct t_unix_ { t_socket sock; t_io io; t_buffer buf; t_timeout tm; } t_unix; typedef t_unix *p_unix; LUASOCKET_API int luaopen_socket_unix(lua_State *L); #endif /* UNIX_H */ ================================================ FILE: src/luasocket/unixdgram.c ================================================ /*=========================================================================*\ * Unix domain socket dgram submodule * LuaSocket toolkit \*=========================================================================*/ #include "luasocket.h" #include "auxiliar.h" #include "socket.h" #include "options.h" #include "unix.h" #include #include #include #define UNIXDGRAM_DATAGRAMSIZE 8192 // provide a SUN_LEN macro if sys/un.h doesn't (e.g. Android) #ifndef SUN_LEN #define SUN_LEN(ptr) \ ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + strlen ((ptr)->sun_path)) #endif /*=========================================================================*\ * Internal function prototypes \*=========================================================================*/ static int global_create(lua_State *L); static int meth_connect(lua_State *L); static int meth_bind(lua_State *L); static int meth_send(lua_State *L); static int meth_receive(lua_State *L); static int meth_close(lua_State *L); static int meth_setoption(lua_State *L); static int meth_settimeout(lua_State *L); static int meth_gettimeout(lua_State *L); static int meth_getfd(lua_State *L); static int meth_setfd(lua_State *L); static int meth_dirty(lua_State *L); static int meth_receivefrom(lua_State *L); static int meth_sendto(lua_State *L); static int meth_getsockname(lua_State *L); static const char *unixdgram_tryconnect(p_unix un, const char *path); static const char *unixdgram_trybind(p_unix un, const char *path); /* unixdgram object methods */ static luaL_Reg unixdgram_methods[] = { {"__gc", meth_close}, {"__tostring", auxiliar_tostring}, {"bind", meth_bind}, {"close", meth_close}, {"connect", meth_connect}, {"dirty", meth_dirty}, {"getfd", meth_getfd}, {"send", meth_send}, {"sendto", meth_sendto}, {"receive", meth_receive}, {"receivefrom", meth_receivefrom}, {"setfd", meth_setfd}, {"setoption", meth_setoption}, {"setpeername", meth_connect}, {"setsockname", meth_bind}, {"getsockname", meth_getsockname}, {"settimeout", meth_settimeout}, {"gettimeout", meth_gettimeout}, {NULL, NULL} }; /* socket option handlers */ static t_opt optset[] = { {"reuseaddr", opt_set_reuseaddr}, {NULL, NULL} }; /* functions in library namespace */ static luaL_Reg func[] = { {"dgram", global_create}, {NULL, NULL} }; /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ int unixdgram_open(lua_State *L) { /* create classes */ auxiliar_newclass(L, "unixdgram{connected}", unixdgram_methods); auxiliar_newclass(L, "unixdgram{unconnected}", unixdgram_methods); /* create class groups */ auxiliar_add2group(L, "unixdgram{connected}", "unixdgram{any}"); auxiliar_add2group(L, "unixdgram{unconnected}", "unixdgram{any}"); auxiliar_add2group(L, "unixdgram{connected}", "select{able}"); auxiliar_add2group(L, "unixdgram{unconnected}", "select{able}"); luaL_setfuncs(L, func, 0); return 0; } /*=========================================================================*\ * Lua methods \*=========================================================================*/ static const char *unixdgram_strerror(int err) { /* a 'closed' error on an unconnected means the target address was not * accepted by the transport layer */ if (err == IO_CLOSED) return "refused"; else return socket_strerror(err); } static int meth_send(lua_State *L) { p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{connected}", 1); p_timeout tm = &un->tm; size_t count, sent = 0; int err; const char *data = luaL_checklstring(L, 2, &count); timeout_markstart(tm); err = socket_send(&un->sock, data, count, &sent, tm); if (err != IO_DONE) { lua_pushnil(L); lua_pushstring(L, unixdgram_strerror(err)); return 2; } lua_pushnumber(L, (lua_Number) sent); return 1; } /*-------------------------------------------------------------------------*\ * Send data through unconnected unixdgram socket \*-------------------------------------------------------------------------*/ static int meth_sendto(lua_State *L) { p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); size_t count, sent = 0; const char *data = luaL_checklstring(L, 2, &count); const char *path = luaL_checkstring(L, 3); p_timeout tm = &un->tm; int err; struct sockaddr_un remote; size_t len = strlen(path); if (len >= sizeof(remote.sun_path)) { lua_pushnil(L); lua_pushstring(L, "path too long"); return 2; } memset(&remote, 0, sizeof(remote)); strcpy(remote.sun_path, path); remote.sun_family = AF_UNIX; timeout_markstart(tm); #ifdef UNIX_HAS_SUN_LEN remote.sun_len = sizeof(remote.sun_family) + sizeof(remote.sun_len) + len + 1; err = socket_sendto(&un->sock, data, count, &sent, (SA *) &remote, remote.sun_len, tm); #else err = socket_sendto(&un->sock, data, count, &sent, (SA *) &remote, sizeof(remote.sun_family) + len, tm); #endif if (err != IO_DONE) { lua_pushnil(L); lua_pushstring(L, unixdgram_strerror(err)); return 2; } lua_pushnumber(L, (lua_Number) sent); return 1; } static int meth_receive(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); char buf[UNIXDGRAM_DATAGRAMSIZE]; size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; int err; p_timeout tm = &un->tm; timeout_markstart(tm); if (!dgram) { lua_pushnil(L); lua_pushliteral(L, "out of memory"); return 2; } err = socket_recv(&un->sock, dgram, wanted, &got, tm); /* Unlike STREAM, recv() of zero is not closed, but a zero-length packet. */ if (err != IO_DONE && err != IO_CLOSED) { lua_pushnil(L); lua_pushstring(L, unixdgram_strerror(err)); if (wanted > sizeof(buf)) free(dgram); return 2; } lua_pushlstring(L, dgram, got); if (wanted > sizeof(buf)) free(dgram); return 1; } /*-------------------------------------------------------------------------*\ * Receives data and sender from a DGRAM socket \*-------------------------------------------------------------------------*/ static int meth_receivefrom(lua_State *L) { p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); char buf[UNIXDGRAM_DATAGRAMSIZE]; size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; struct sockaddr_un addr; socklen_t addr_len = sizeof(addr); int err; p_timeout tm = &un->tm; timeout_markstart(tm); if (!dgram) { lua_pushnil(L); lua_pushliteral(L, "out of memory"); return 2; } addr.sun_path[0] = '\0'; err = socket_recvfrom(&un->sock, dgram, wanted, &got, (SA *) &addr, &addr_len, tm); /* Unlike STREAM, recv() of zero is not closed, but a zero-length packet. */ if (err != IO_DONE && err != IO_CLOSED) { lua_pushnil(L); lua_pushstring(L, unixdgram_strerror(err)); if (wanted > sizeof(buf)) free(dgram); return 2; } lua_pushlstring(L, dgram, got); /* the path may be empty, when client send without bind */ lua_pushstring(L, addr.sun_path); if (wanted > sizeof(buf)) free(dgram); return 2; } /*-------------------------------------------------------------------------*\ * Just call option handler \*-------------------------------------------------------------------------*/ static int meth_setoption(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); return opt_meth_setoption(L, optset, &un->sock); } /*-------------------------------------------------------------------------*\ * Select support methods \*-------------------------------------------------------------------------*/ static int meth_getfd(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); lua_pushnumber(L, (int) un->sock); return 1; } /* this is very dangerous, but can be handy for those that are brave enough */ static int meth_setfd(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); un->sock = (t_socket) luaL_checknumber(L, 2); return 0; } static int meth_dirty(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); (void) un; lua_pushboolean(L, 0); return 1; } /*-------------------------------------------------------------------------*\ * Binds an object to an address \*-------------------------------------------------------------------------*/ static const char *unixdgram_trybind(p_unix un, const char *path) { struct sockaddr_un local; size_t len = strlen(path); if (len >= sizeof(local.sun_path)) return "path too long"; memset(&local, 0, sizeof(local)); strcpy(local.sun_path, path); local.sun_family = AF_UNIX; size_t addrlen = SUN_LEN(&local); #ifdef UNIX_HAS_SUN_LEN local.sun_len = addrlen + 1; #endif int err = socket_bind(&un->sock, (SA *) &local, addrlen); if (err != IO_DONE) socket_destroy(&un->sock); return socket_strerror(err); } static int meth_bind(lua_State *L) { p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); const char *path = luaL_checkstring(L, 2); const char *err = unixdgram_trybind(un, path); if (err) { lua_pushnil(L); lua_pushstring(L, err); return 2; } lua_pushnumber(L, 1); return 1; } static int meth_getsockname(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); struct sockaddr_un peer = {0}; socklen_t peer_len = sizeof(peer); if (getsockname(un->sock, (SA *) &peer, &peer_len) < 0) { lua_pushnil(L); lua_pushstring(L, socket_strerror(errno)); return 2; } lua_pushstring(L, peer.sun_path); return 1; } /*-------------------------------------------------------------------------*\ * Turns a master unixdgram object into a client object. \*-------------------------------------------------------------------------*/ static const char *unixdgram_tryconnect(p_unix un, const char *path) { struct sockaddr_un remote; size_t len = strlen(path); if (len >= sizeof(remote.sun_path)) return "path too long"; memset(&remote, 0, sizeof(remote)); strcpy(remote.sun_path, path); remote.sun_family = AF_UNIX; timeout_markstart(&un->tm); size_t addrlen = SUN_LEN(&remote); #ifdef UNIX_HAS_SUN_LEN remote.sun_len = addrlen + 1; #endif int err = socket_connect(&un->sock, (SA *) &remote, addrlen, &un->tm); if (err != IO_DONE) socket_destroy(&un->sock); return socket_strerror(err); } static int meth_connect(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); const char *path = luaL_checkstring(L, 2); const char *err = unixdgram_tryconnect(un, path); if (err) { lua_pushnil(L); lua_pushstring(L, err); return 2; } /* turn unconnected object into a connected object */ auxiliar_setclass(L, "unixdgram{connected}", 1); lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Closes socket used by object \*-------------------------------------------------------------------------*/ static int meth_close(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); socket_destroy(&un->sock); lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Just call tm methods \*-------------------------------------------------------------------------*/ static int meth_settimeout(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); return timeout_meth_settimeout(L, &un->tm); } static int meth_gettimeout(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); return timeout_meth_gettimeout(L, &un->tm); } /*=========================================================================*\ * Library functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Creates a master unixdgram object \*-------------------------------------------------------------------------*/ static int global_create(lua_State *L) { t_socket sock; int err = socket_create(&sock, AF_UNIX, SOCK_DGRAM, 0); /* try to allocate a system socket */ if (err == IO_DONE) { /* allocate unixdgram object */ p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); /* set its type as master object */ auxiliar_setclass(L, "unixdgram{unconnected}", -1); /* initialize remaining structure fields */ socket_setnonblocking(&sock); un->sock = sock; io_init(&un->io, (p_send) socket_send, (p_recv) socket_recv, (p_error) socket_ioerror, &un->sock); timeout_init(&un->tm, -1, -1); buffer_init(&un->buf, &un->io, &un->tm); return 1; } else { lua_pushnil(L); lua_pushstring(L, socket_strerror(err)); return 2; } } ================================================ FILE: src/luasocket/unixdgram.h ================================================ #ifndef UNIXDGRAM_H #define UNIXDGRAM_H /*=========================================================================*\ * DGRAM object * LuaSocket toolkit * * The dgram.h module provides LuaSocket with support for DGRAM protocol * (AF_INET, SOCK_DGRAM). * * Two classes are defined: connected and unconnected. DGRAM objects are * originally unconnected. They can be "connected" to a given address * with a call to the setpeername function. The same function can be used to * break the connection. \*=========================================================================*/ #include "unix.h" #ifndef _WIN32 #pragma GCC visibility push(hidden) #endif int unixdgram_open(lua_State *L); #ifndef _WIN32 #pragma GCC visibility pop #endif #endif /* UNIXDGRAM_H */ ================================================ FILE: src/luasocket/unixstream.c ================================================ /*=========================================================================*\ * Unix domain socket stream sub module * LuaSocket toolkit \*=========================================================================*/ #include "luasocket.h" #include "auxiliar.h" #include "socket.h" #include "options.h" #include "unixstream.h" #include #include /*=========================================================================*\ * Internal function prototypes \*=========================================================================*/ static int global_create(lua_State *L); static int meth_connect(lua_State *L); static int meth_listen(lua_State *L); static int meth_bind(lua_State *L); static int meth_send(lua_State *L); static int meth_shutdown(lua_State *L); static int meth_receive(lua_State *L); static int meth_accept(lua_State *L); static int meth_close(lua_State *L); static int meth_setoption(lua_State *L); static int meth_settimeout(lua_State *L); static int meth_getfd(lua_State *L); static int meth_setfd(lua_State *L); static int meth_dirty(lua_State *L); static int meth_getstats(lua_State *L); static int meth_setstats(lua_State *L); static int meth_getsockname(lua_State *L); static const char *unixstream_tryconnect(p_unix un, const char *path); static const char *unixstream_trybind(p_unix un, const char *path); /* unixstream object methods */ static luaL_Reg unixstream_methods[] = { {"__gc", meth_close}, {"__tostring", auxiliar_tostring}, {"accept", meth_accept}, {"bind", meth_bind}, {"close", meth_close}, {"connect", meth_connect}, {"dirty", meth_dirty}, {"getfd", meth_getfd}, {"getstats", meth_getstats}, {"setstats", meth_setstats}, {"listen", meth_listen}, {"receive", meth_receive}, {"send", meth_send}, {"setfd", meth_setfd}, {"setoption", meth_setoption}, {"setpeername", meth_connect}, {"setsockname", meth_bind}, {"getsockname", meth_getsockname}, {"settimeout", meth_settimeout}, {"shutdown", meth_shutdown}, {NULL, NULL} }; /* socket option handlers */ static t_opt optset[] = { {"keepalive", opt_set_keepalive}, {"reuseaddr", opt_set_reuseaddr}, {"linger", opt_set_linger}, {NULL, NULL} }; /* functions in library namespace */ static luaL_Reg func[] = { {"stream", global_create}, {NULL, NULL} }; /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ int unixstream_open(lua_State *L) { /* create classes */ auxiliar_newclass(L, "unixstream{master}", unixstream_methods); auxiliar_newclass(L, "unixstream{client}", unixstream_methods); auxiliar_newclass(L, "unixstream{server}", unixstream_methods); /* create class groups */ auxiliar_add2group(L, "unixstream{master}", "unixstream{any}"); auxiliar_add2group(L, "unixstream{client}", "unixstream{any}"); auxiliar_add2group(L, "unixstream{server}", "unixstream{any}"); luaL_setfuncs(L, func, 0); return 0; } /*=========================================================================*\ * Lua methods \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Just call buffered IO methods \*-------------------------------------------------------------------------*/ static int meth_send(lua_State *L) { p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); return buffer_meth_send(L, &un->buf); } static int meth_receive(lua_State *L) { p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); return buffer_meth_receive(L, &un->buf); } static int meth_getstats(lua_State *L) { p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); return buffer_meth_getstats(L, &un->buf); } static int meth_setstats(lua_State *L) { p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); return buffer_meth_setstats(L, &un->buf); } /*-------------------------------------------------------------------------*\ * Just call option handler \*-------------------------------------------------------------------------*/ static int meth_setoption(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); return opt_meth_setoption(L, optset, &un->sock); } /*-------------------------------------------------------------------------*\ * Select support methods \*-------------------------------------------------------------------------*/ static int meth_getfd(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); lua_pushnumber(L, (int) un->sock); return 1; } /* this is very dangerous, but can be handy for those that are brave enough */ static int meth_setfd(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); un->sock = (t_socket) luaL_checknumber(L, 2); return 0; } static int meth_dirty(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); lua_pushboolean(L, !buffer_isempty(&un->buf)); return 1; } /*-------------------------------------------------------------------------*\ * Waits for and returns a client object attempting connection to the * server object \*-------------------------------------------------------------------------*/ static int meth_accept(lua_State *L) { p_unix server = (p_unix) auxiliar_checkclass(L, "unixstream{server}", 1); p_timeout tm = timeout_markstart(&server->tm); t_socket sock; int err = socket_accept(&server->sock, &sock, NULL, NULL, tm); /* if successful, push client socket */ if (err == IO_DONE) { p_unix clnt = (p_unix) lua_newuserdata(L, sizeof(t_unix)); auxiliar_setclass(L, "unixstream{client}", -1); /* initialize structure fields */ socket_setnonblocking(&sock); clnt->sock = sock; io_init(&clnt->io, (p_send)socket_send, (p_recv)socket_recv, (p_error) socket_ioerror, &clnt->sock); timeout_init(&clnt->tm, -1, -1); buffer_init(&clnt->buf, &clnt->io, &clnt->tm); return 1; } else { lua_pushnil(L); lua_pushstring(L, socket_strerror(err)); return 2; } } /*-------------------------------------------------------------------------*\ * Binds an object to an address \*-------------------------------------------------------------------------*/ static const char *unixstream_trybind(p_unix un, const char *path) { struct sockaddr_un local; size_t len = strlen(path); int err; if (len >= sizeof(local.sun_path)) return "path too long"; memset(&local, 0, sizeof(local)); strcpy(local.sun_path, path); local.sun_family = AF_UNIX; #ifdef UNIX_HAS_SUN_LEN local.sun_len = sizeof(local.sun_family) + sizeof(local.sun_len) + len + 1; err = socket_bind(&un->sock, (SA *) &local, local.sun_len); #else err = socket_bind(&un->sock, (SA *) &local, sizeof(local.sun_family) + len); #endif if (err != IO_DONE) socket_destroy(&un->sock); return socket_strerror(err); } static int meth_bind(lua_State *L) { p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); const char *path = luaL_checkstring(L, 2); const char *err = unixstream_trybind(un, path); if (err) { lua_pushnil(L); lua_pushstring(L, err); return 2; } lua_pushnumber(L, 1); return 1; } static int meth_getsockname(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); struct sockaddr_un peer = {0}; socklen_t peer_len = sizeof(peer); if (getsockname(un->sock, (SA *) &peer, &peer_len) < 0) { lua_pushnil(L); lua_pushstring(L, socket_strerror(errno)); return 2; } lua_pushstring(L, peer.sun_path); return 1; } /*-------------------------------------------------------------------------*\ * Turns a master unixstream object into a client object. \*-------------------------------------------------------------------------*/ static const char *unixstream_tryconnect(p_unix un, const char *path) { struct sockaddr_un remote; int err; size_t len = strlen(path); if (len >= sizeof(remote.sun_path)) return "path too long"; memset(&remote, 0, sizeof(remote)); strcpy(remote.sun_path, path); remote.sun_family = AF_UNIX; timeout_markstart(&un->tm); #ifdef UNIX_HAS_SUN_LEN remote.sun_len = sizeof(remote.sun_family) + sizeof(remote.sun_len) + len + 1; err = socket_connect(&un->sock, (SA *) &remote, remote.sun_len, &un->tm); #else err = socket_connect(&un->sock, (SA *) &remote, sizeof(remote.sun_family) + len, &un->tm); #endif if (err != IO_DONE) socket_destroy(&un->sock); return socket_strerror(err); } static int meth_connect(lua_State *L) { p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); const char *path = luaL_checkstring(L, 2); const char *err = unixstream_tryconnect(un, path); if (err) { lua_pushnil(L); lua_pushstring(L, err); return 2; } /* turn master object into a client object */ auxiliar_setclass(L, "unixstream{client}", 1); lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Closes socket used by object \*-------------------------------------------------------------------------*/ static int meth_close(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); socket_destroy(&un->sock); lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Puts the sockt in listen mode \*-------------------------------------------------------------------------*/ static int meth_listen(lua_State *L) { p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); int backlog = (int) luaL_optnumber(L, 2, 32); int err = socket_listen(&un->sock, backlog); if (err != IO_DONE) { lua_pushnil(L); lua_pushstring(L, socket_strerror(err)); return 2; } /* turn master object into a server object */ auxiliar_setclass(L, "unixstream{server}", 1); lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Shuts the connection down partially \*-------------------------------------------------------------------------*/ static int meth_shutdown(lua_State *L) { /* SHUT_RD, SHUT_WR, SHUT_RDWR have the value 0, 1, 2, so we can use method index directly */ static const char* methods[] = { "receive", "send", "both", NULL }; p_unix stream = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); int how = luaL_checkoption(L, 2, "both", methods); socket_shutdown(&stream->sock, how); lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Just call tm methods \*-------------------------------------------------------------------------*/ static int meth_settimeout(lua_State *L) { p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); return timeout_meth_settimeout(L, &un->tm); } /*=========================================================================*\ * Library functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Creates a master unixstream object \*-------------------------------------------------------------------------*/ static int global_create(lua_State *L) { t_socket sock; int err = socket_create(&sock, AF_UNIX, SOCK_STREAM, 0); /* try to allocate a system socket */ if (err == IO_DONE) { /* allocate unixstream object */ p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); /* set its type as master object */ auxiliar_setclass(L, "unixstream{master}", -1); /* initialize remaining structure fields */ socket_setnonblocking(&sock); un->sock = sock; io_init(&un->io, (p_send) socket_send, (p_recv) socket_recv, (p_error) socket_ioerror, &un->sock); timeout_init(&un->tm, -1, -1); buffer_init(&un->buf, &un->io, &un->tm); return 1; } else { lua_pushnil(L); lua_pushstring(L, socket_strerror(err)); return 2; } } ================================================ FILE: src/luasocket/unixstream.h ================================================ #ifndef UNIXSTREAM_H #define UNIXSTREAM_H /*=========================================================================*\ * UNIX STREAM object * LuaSocket toolkit * * The unixstream.h module is basicly a glue that puts together modules buffer.h, * timeout.h socket.h and inet.h to provide the LuaSocket UNIX STREAM (AF_UNIX, * SOCK_STREAM) support. * * Three classes are defined: master, client and server. The master class is * a newly created unixstream object, that has not been bound or connected. Server * objects are unixstream objects bound to some local address. Client objects are * unixstream objects either connected to some address or returned by the accept * method of a server object. \*=========================================================================*/ #include "unix.h" #ifndef _WIN32 #pragma GCC visibility push(hidden) #endif int unixstream_open(lua_State *L); #ifndef _WIN32 #pragma GCC visibility pop #endif #endif /* UNIXSTREAM_H */ ================================================ FILE: src/luasocket/url.lua ================================================ ----------------------------------------------------------------------------- -- URI parsing, composition and relative URL resolution -- LuaSocket toolkit. -- Author: Diego Nehab ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- -- Declare module ----------------------------------------------------------------------------- socket.url = {} local _M = socket.url ----------------------------------------------------------------------------- -- Module version ----------------------------------------------------------------------------- _M._VERSION = "URL 1.0.3" ----------------------------------------------------------------------------- -- Encodes a string into its escaped hexadecimal representation -- Input -- s: binary string to be encoded -- Returns -- escaped representation of string binary ----------------------------------------------------------------------------- function _M.escape(s) return (string.gsub(s, "([^A-Za-z0-9_])", function(c) return string.format("%%%02x", string.byte(c)) end)) end ----------------------------------------------------------------------------- -- Protects a path segment, to prevent it from interfering with the -- url parsing. -- Input -- s: binary string to be encoded -- Returns -- escaped representation of string binary ----------------------------------------------------------------------------- local function make_set(t) local s = {} for i,v in ipairs(t) do s[t[i]] = 1 end return s end -- these are allowed within a path segment, along with alphanum -- other characters must be escaped local segment_set = make_set { "-", "_", ".", "!", "~", "*", "'", "(", ")", ":", "@", "&", "=", "+", "$", ",", } local function protect_segment(s) return string.gsub(s, "([^A-Za-z0-9_])", function (c) if segment_set[c] then return c else return string.format("%%%02X", string.byte(c)) end end) end ----------------------------------------------------------------------------- -- Unencodes a escaped hexadecimal string into its binary representation -- Input -- s: escaped hexadecimal string to be unencoded -- Returns -- unescaped binary representation of escaped hexadecimal binary ----------------------------------------------------------------------------- function _M.unescape(s) return (string.gsub(s, "%%(%x%x)", function(hex) return string.char(tonumber(hex, 16)) end)) end ----------------------------------------------------------------------------- -- Removes '..' and '.' components appropriately from a path. -- Input -- path -- Returns -- dot-normalized path local function remove_dot_components(path) local marker = string.char(1) repeat local was = path path = path:gsub('//', '/'..marker..'/', 1) until path == was repeat local was = path path = path:gsub('/%./', '/', 1) until path == was repeat local was = path path = path:gsub('[^/]+/%.%./([^/]+)', '%1', 1) until path == was path = path:gsub('[^/]+/%.%./*$', '') path = path:gsub('/%.%.$', '/') path = path:gsub('/%.$', '/') path = path:gsub('^/%.%./', '/') path = path:gsub(marker, '') return path end ----------------------------------------------------------------------------- -- Builds a path from a base path and a relative path -- Input -- base_path -- relative_path -- Returns -- corresponding absolute path ----------------------------------------------------------------------------- local function absolute_path(base_path, relative_path) if string.sub(relative_path, 1, 1) == "/" then return remove_dot_components(relative_path) end base_path = base_path:gsub("[^/]*$", "") if not base_path:find'/$' then base_path = base_path .. '/' end local path = base_path .. relative_path path = remove_dot_components(path) return path end ----------------------------------------------------------------------------- -- Parses a url and returns a table with all its parts according to RFC 2396 -- The following grammar describes the names given to the URL parts -- ::= :///;?# -- ::= @: -- ::= [:] -- :: = {/} -- Input -- url: uniform resource locator of request -- default: table with default values for each field -- Returns -- table with the following fields, where RFC naming conventions have -- been preserved: -- scheme, authority, userinfo, user, password, host, port, -- path, params, query, fragment -- Obs: -- the leading '/' in {/} is considered part of ----------------------------------------------------------------------------- function _M.parse(url, default) -- initialize default parameters local parsed = {} for i,v in pairs(default or parsed) do parsed[i] = v end -- empty url is parsed to nil if not url or url == "" then return nil, "invalid url" end -- remove whitespace -- url = string.gsub(url, "%s", "") -- get scheme url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", function(s) parsed.scheme = s; return "" end) -- get authority url = string.gsub(url, "^//([^/]*)", function(n) parsed.authority = n return "" end) -- get fragment url = string.gsub(url, "#(.*)$", function(f) parsed.fragment = f return "" end) -- get query string url = string.gsub(url, "%?(.*)", function(q) parsed.query = q return "" end) -- get params url = string.gsub(url, "%;(.*)", function(p) parsed.params = p return "" end) -- path is whatever was left if url ~= "" then parsed.path = url end local authority = parsed.authority if not authority then return parsed end authority = string.gsub(authority,"^([^@]*)@", function(u) parsed.userinfo = u; return "" end) authority = string.gsub(authority, ":([^:%]]*)$", function(p) parsed.port = p; return "" end) if authority ~= "" then -- IPv6? parsed.host = string.match(authority, "^%[(.+)%]$") or authority end local userinfo = parsed.userinfo if not userinfo then return parsed end userinfo = string.gsub(userinfo, ":([^:]*)$", function(p) parsed.password = p; return "" end) parsed.user = userinfo return parsed end ----------------------------------------------------------------------------- -- Rebuilds a parsed URL from its components. -- Components are protected if any reserved or unallowed characters are found -- Input -- parsed: parsed URL, as returned by parse -- Returns -- a stringing with the corresponding URL ----------------------------------------------------------------------------- function _M.build(parsed) --local ppath = _M.parse_path(parsed.path or "") --local url = _M.build_path(ppath) local url = parsed.path or "" if parsed.params then url = url .. ";" .. parsed.params end if parsed.query then url = url .. "?" .. parsed.query end local authority = parsed.authority if parsed.host then authority = parsed.host if string.find(authority, ":") then -- IPv6? authority = "[" .. authority .. "]" end if parsed.port then authority = authority .. ":" .. tostring(parsed.port) end local userinfo = parsed.userinfo if parsed.user then userinfo = parsed.user if parsed.password then userinfo = userinfo .. ":" .. parsed.password end end if userinfo then authority = userinfo .. "@" .. authority end end if authority then url = "//" .. authority .. url end if parsed.scheme then url = parsed.scheme .. ":" .. url end if parsed.fragment then url = url .. "#" .. parsed.fragment end -- url = string.gsub(url, "%s", "") return url end ----------------------------------------------------------------------------- -- Builds a absolute URL from a base and a relative URL according to RFC 2396 -- Input -- base_url -- relative_url -- Returns -- corresponding absolute url ----------------------------------------------------------------------------- function _M.absolute(base_url, relative_url) local base_parsed if type(base_url) == "table" then base_parsed = base_url base_url = _M.build(base_parsed) else base_parsed = _M.parse(base_url) end local result local relative_parsed = _M.parse(relative_url) if not base_parsed then result = relative_url elseif not relative_parsed then result = base_url elseif relative_parsed.scheme then result = relative_url else relative_parsed.scheme = base_parsed.scheme if not relative_parsed.authority then relative_parsed.authority = base_parsed.authority if not relative_parsed.path then relative_parsed.path = base_parsed.path if not relative_parsed.params then relative_parsed.params = base_parsed.params if not relative_parsed.query then relative_parsed.query = base_parsed.query end end else relative_parsed.path = absolute_path(base_parsed.path or "", relative_parsed.path) end end result = _M.build(relative_parsed) end return remove_dot_components(result) end ----------------------------------------------------------------------------- -- Breaks a path into its segments, unescaping the segments -- Input -- path -- Returns -- segment: a table with one entry per segment ----------------------------------------------------------------------------- function _M.parse_path(path) local parsed = {} path = path or "" --path = string.gsub(path, "%s", "") string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) for i = 1, #parsed do parsed[i] = _M.unescape(parsed[i]) end if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end return parsed end ----------------------------------------------------------------------------- -- Builds a path component from its segments, escaping protected characters. -- Input -- parsed: path segments -- unsafe: if true, segments are not protected before path is built -- Returns -- path: corresponding path stringing ----------------------------------------------------------------------------- function _M.build_path(parsed, unsafe) local path = "" local n = #parsed if unsafe then for i = 1, n-1 do path = path .. parsed[i] path = path .. "/" end if n > 0 then path = path .. parsed[n] if parsed.is_directory then path = path .. "/" end end else for i = 1, n-1 do path = path .. protect_segment(parsed[i]) path = path .. "/" end if n > 0 then path = path .. protect_segment(parsed[n]) if parsed.is_directory then path = path .. "/" end end end if parsed.is_absolute then path = "/" .. path end return path end return _M ================================================ FILE: src/luasocket/usocket.c ================================================ /*=========================================================================*\ * Socket compatibilization module for Unix * LuaSocket toolkit * * The code is now interrupt-safe. * The penalty of calling select to avoid busy-wait is only paid when * the I/O call fail in the first place. \*=========================================================================*/ #include "luasocket.h" #include "../teliva.h" #include "socket.h" #include "pierror.h" #include #include /*-------------------------------------------------------------------------*\ * Wait for readable/writable/connected socket with timeout \*-------------------------------------------------------------------------*/ #ifndef SOCKET_SELECT #include #define WAITFD_R POLLIN #define WAITFD_W POLLOUT #define WAITFD_C (POLLIN|POLLOUT) int socket_waitfd(p_socket ps, int sw, p_timeout tm) { int ret; struct pollfd pfd; pfd.fd = *ps; pfd.events = sw; pfd.revents = 0; if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */ do { int t = (int)(timeout_getretry(tm)*1e3); ret = poll(&pfd, 1, t >= 0? t: -1); } while (ret == -1 && errno == EINTR); if (ret == -1) return errno; if (ret == 0) return IO_TIMEOUT; if (sw == WAITFD_C && (pfd.revents & (POLLIN|POLLERR))) return IO_CLOSED; return IO_DONE; } #else #define WAITFD_R 1 #define WAITFD_W 2 #define WAITFD_C (WAITFD_R|WAITFD_W) int socket_waitfd(p_socket ps, int sw, p_timeout tm) { int ret; fd_set rfds, wfds, *rp, *wp; struct timeval tv, *tp; double t; if (*ps >= FD_SETSIZE) return EINVAL; if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */ do { /* must set bits within loop, because select may have modifed them */ rp = wp = NULL; if (sw & WAITFD_R) { FD_ZERO(&rfds); FD_SET(*ps, &rfds); rp = &rfds; } if (sw & WAITFD_W) { FD_ZERO(&wfds); FD_SET(*ps, &wfds); wp = &wfds; } t = timeout_getretry(tm); tp = NULL; if (t >= 0.0) { tv.tv_sec = (int)t; tv.tv_usec = (int)((t-tv.tv_sec)*1.0e6); tp = &tv; } ret = select(*ps+1, rp, wp, NULL, tp); } while (ret == -1 && errno == EINTR); if (ret == -1) return errno; if (ret == 0) return IO_TIMEOUT; if (sw == WAITFD_C && FD_ISSET(*ps, &rfds)) return IO_CLOSED; return IO_DONE; } #endif /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ int socket_open(void) { /* installs a handler to ignore sigpipe or it will crash us */ signal(SIGPIPE, SIG_IGN); return 1; } /*-------------------------------------------------------------------------*\ * Close module \*-------------------------------------------------------------------------*/ int socket_close(void) { return 1; } /*-------------------------------------------------------------------------*\ * Close and initialize socket \*-------------------------------------------------------------------------*/ void socket_destroy(p_socket ps) { if (*ps != SOCKET_INVALID) { close(*ps); *ps = SOCKET_INVALID; } } /*-------------------------------------------------------------------------*\ * Select with timeout control \*-------------------------------------------------------------------------*/ int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds, p_timeout tm) { int ret; do { struct timeval tv; double t = timeout_getretry(tm); tv.tv_sec = (int) t; tv.tv_usec = (int) ((t - tv.tv_sec) * 1.0e6); /* timeout = 0 means no wait */ ret = select(n, rfds, wfds, efds, t >= 0.0 ? &tv: NULL); } while (ret < 0 && errno == EINTR); return ret; } /*-------------------------------------------------------------------------*\ * Creates and sets up a socket \*-------------------------------------------------------------------------*/ int socket_create(p_socket ps, int domain, int type, int protocol) { *ps = socket(domain, type, protocol); if (*ps != SOCKET_INVALID) return IO_DONE; else return errno; } /*-------------------------------------------------------------------------*\ * Binds or returns error message \*-------------------------------------------------------------------------*/ int socket_bind(p_socket ps, SA *addr, socklen_t len) { int err = IO_DONE; if (!net_operations_permitted) { Previous_message = "app tried to start a server; adjust its permissions (ctrl-p) if that is expected"; return IO_CLOSED; } socket_setblocking(ps); if (bind(*ps, addr, len) < 0) err = errno; socket_setnonblocking(ps); return err; } /*-------------------------------------------------------------------------*\ * \*-------------------------------------------------------------------------*/ int socket_listen(p_socket ps, int backlog) { int err = IO_DONE; if (listen(*ps, backlog)) err = errno; return err; } /*-------------------------------------------------------------------------*\ * \*-------------------------------------------------------------------------*/ void socket_shutdown(p_socket ps, int how) { shutdown(*ps, how); } /*-------------------------------------------------------------------------*\ * Connects or returns error message \*-------------------------------------------------------------------------*/ int socket_connect(p_socket ps, SA *addr, socklen_t len, p_timeout tm) { int err; /* avoid calling on closed sockets */ if (*ps == SOCKET_INVALID) return IO_CLOSED; if (!net_operations_permitted) { Previous_message = "app tried to connect to a remote server; adjust its permissions (ctrl-p) if that is expected"; return IO_CLOSED; } /* call connect until done or failed without being interrupted */ do if (connect(*ps, addr, len) == 0) return IO_DONE; while ((err = errno) == EINTR); /* if connection failed immediately, return error code */ if (err != EINPROGRESS && err != EAGAIN) return err; /* zero timeout case optimization */ if (timeout_iszero(tm)) return IO_TIMEOUT; /* wait until we have the result of the connection attempt or timeout */ err = socket_waitfd(ps, WAITFD_C, tm); if (err == IO_CLOSED) { if (recv(*ps, (char *) &err, 0, 0) == 0) return IO_DONE; else return errno; } else return err; } /*-------------------------------------------------------------------------*\ * Accept with timeout \*-------------------------------------------------------------------------*/ int socket_accept(p_socket ps, p_socket pa, SA *addr, socklen_t *len, p_timeout tm) { if (*ps == SOCKET_INVALID) return IO_CLOSED; for ( ;; ) { int err; if ((*pa = accept(*ps, addr, len)) != SOCKET_INVALID) return IO_DONE; err = errno; if (err == EINTR) continue; if (err != EAGAIN && err != ECONNABORTED) return err; if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; } /* can't reach here */ return IO_UNKNOWN; } /*-------------------------------------------------------------------------*\ * Send with timeout \*-------------------------------------------------------------------------*/ int socket_send(p_socket ps, const char *data, size_t count, size_t *sent, p_timeout tm) { int err; *sent = 0; /* avoid making system calls on closed sockets */ if (*ps == SOCKET_INVALID) return IO_CLOSED; /* loop until we send something or we give up on error */ for ( ;; ) { long put = (long) send(*ps, data, count, 0); /* if we sent anything, we are done */ if (put >= 0) { *sent = put; return IO_DONE; } err = errno; /* EPIPE means the connection was closed */ if (err == EPIPE) return IO_CLOSED; /* EPROTOTYPE means the connection is being closed (on Yosemite!)*/ if (err == EPROTOTYPE) continue; /* we call was interrupted, just try again */ if (err == EINTR) continue; /* if failed fatal reason, report error */ if (err != EAGAIN) return err; /* wait until we can send something or we timeout */ if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; } /* can't reach here */ return IO_UNKNOWN; } /*-------------------------------------------------------------------------*\ * Sendto with timeout \*-------------------------------------------------------------------------*/ int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent, SA *addr, socklen_t len, p_timeout tm) { int err; *sent = 0; if (*ps == SOCKET_INVALID) return IO_CLOSED; for ( ;; ) { long put = (long) sendto(*ps, data, count, 0, addr, len); if (put >= 0) { *sent = put; return IO_DONE; } err = errno; if (err == EPIPE) return IO_CLOSED; if (err == EPROTOTYPE) continue; if (err == EINTR) continue; if (err != EAGAIN) return err; if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; } return IO_UNKNOWN; } /*-------------------------------------------------------------------------*\ * Receive with timeout \*-------------------------------------------------------------------------*/ int socket_recv(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm) { int err; *got = 0; if (*ps == SOCKET_INVALID) return IO_CLOSED; for ( ;; ) { long taken = (long) recv(*ps, data, count, 0); if (taken > 0) { *got = taken; return IO_DONE; } err = errno; if (taken == 0) return IO_CLOSED; if (err == EINTR) continue; if (err != EAGAIN) return err; if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; } return IO_UNKNOWN; } /*-------------------------------------------------------------------------*\ * Recvfrom with timeout \*-------------------------------------------------------------------------*/ int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got, SA *addr, socklen_t *len, p_timeout tm) { int err; *got = 0; if (*ps == SOCKET_INVALID) return IO_CLOSED; for ( ;; ) { long taken = (long) recvfrom(*ps, data, count, 0, addr, len); if (taken > 0) { *got = taken; return IO_DONE; } err = errno; if (taken == 0) return IO_CLOSED; if (err == EINTR) continue; if (err != EAGAIN) return err; if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; } return IO_UNKNOWN; } /*-------------------------------------------------------------------------*\ * Write with timeout * * socket_read and socket_write are cut-n-paste of socket_send and socket_recv, * with send/recv replaced with write/read. We can't just use write/read * in the socket version, because behaviour when size is zero is different. \*-------------------------------------------------------------------------*/ int socket_write(p_socket ps, const char *data, size_t count, size_t *sent, p_timeout tm) { int err; *sent = 0; /* avoid making system calls on closed sockets */ if (*ps == SOCKET_INVALID) return IO_CLOSED; /* loop until we send something or we give up on error */ for ( ;; ) { long put = (long) write(*ps, data, count); /* if we sent anything, we are done */ if (put >= 0) { *sent = put; return IO_DONE; } err = errno; /* EPIPE means the connection was closed */ if (err == EPIPE) return IO_CLOSED; /* EPROTOTYPE means the connection is being closed (on Yosemite!)*/ if (err == EPROTOTYPE) continue; /* we call was interrupted, just try again */ if (err == EINTR) continue; /* if failed fatal reason, report error */ if (err != EAGAIN) return err; /* wait until we can send something or we timeout */ if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; } /* can't reach here */ return IO_UNKNOWN; } /*-------------------------------------------------------------------------*\ * Read with timeout * See note for socket_write \*-------------------------------------------------------------------------*/ int socket_read(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm) { int err; *got = 0; if (*ps == SOCKET_INVALID) return IO_CLOSED; for ( ;; ) { long taken = (long) read(*ps, data, count); if (taken > 0) { *got = taken; return IO_DONE; } err = errno; if (taken == 0) return IO_CLOSED; if (err == EINTR) continue; if (err != EAGAIN) return err; if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; } return IO_UNKNOWN; } /*-------------------------------------------------------------------------*\ * Put socket into blocking mode \*-------------------------------------------------------------------------*/ void socket_setblocking(p_socket ps) { int flags = fcntl(*ps, F_GETFL, 0); flags &= (~(O_NONBLOCK)); fcntl(*ps, F_SETFL, flags); } /*-------------------------------------------------------------------------*\ * Put socket into non-blocking mode \*-------------------------------------------------------------------------*/ void socket_setnonblocking(p_socket ps) { int flags = fcntl(*ps, F_GETFL, 0); flags |= O_NONBLOCK; fcntl(*ps, F_SETFL, flags); } /*-------------------------------------------------------------------------*\ * DNS helpers \*-------------------------------------------------------------------------*/ int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp) { *hp = gethostbyaddr(addr, len, AF_INET); if (*hp) return IO_DONE; else if (h_errno) return h_errno; else if (errno) return errno; else return IO_UNKNOWN; } int socket_gethostbyname(const char *addr, struct hostent **hp) { *hp = gethostbyname(addr); if (*hp) return IO_DONE; else if (h_errno) return h_errno; else if (errno) return errno; else return IO_UNKNOWN; } /*-------------------------------------------------------------------------*\ * Error translation functions * Make sure important error messages are standard \*-------------------------------------------------------------------------*/ const char *socket_hoststrerror(int err) { if (err <= 0) return io_strerror(err); switch (err) { case HOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; default: return hstrerror(err); } } const char *socket_strerror(int err) { if (err <= 0) return io_strerror(err); switch (err) { case EADDRINUSE: return PIE_ADDRINUSE; case EISCONN: return PIE_ISCONN; case EACCES: return PIE_ACCESS; case ECONNREFUSED: return PIE_CONNREFUSED; case ECONNABORTED: return PIE_CONNABORTED; case ECONNRESET: return PIE_CONNRESET; case ETIMEDOUT: return PIE_TIMEDOUT; default: { return strerror(err); } } } const char *socket_ioerror(p_socket ps, int err) { (void) ps; return socket_strerror(err); } const char *socket_gaistrerror(int err) { if (err == 0) return NULL; switch (err) { case EAI_AGAIN: return PIE_AGAIN; case EAI_BADFLAGS: return PIE_BADFLAGS; #ifdef EAI_BADHINTS case EAI_BADHINTS: return PIE_BADHINTS; #endif case EAI_FAIL: return PIE_FAIL; case EAI_FAMILY: return PIE_FAMILY; case EAI_MEMORY: return PIE_MEMORY; case EAI_NONAME: return PIE_NONAME; #ifdef EAI_OVERFLOW case EAI_OVERFLOW: return PIE_OVERFLOW; #endif #ifdef EAI_PROTOCOL case EAI_PROTOCOL: return PIE_PROTOCOL; #endif case EAI_SERVICE: return PIE_SERVICE; case EAI_SOCKTYPE: return PIE_SOCKTYPE; case EAI_SYSTEM: return strerror(errno); default: return gai_strerror(err); } } ================================================ FILE: src/luasocket/usocket.h ================================================ #ifndef USOCKET_H #define USOCKET_H /*=========================================================================*\ * Socket compatibilization module for Unix * LuaSocket toolkit \*=========================================================================*/ /*=========================================================================*\ * BSD include files \*=========================================================================*/ /* error codes */ #include /* close function */ #include /* fnctnl function and associated constants */ #include /* struct sockaddr */ #include /* socket function */ #include /* struct timeval */ #include /* gethostbyname and gethostbyaddr functions */ #include /* sigpipe handling */ #include /* IP stuff*/ #include #include /* TCP options (nagle algorithm disable) */ #include #include #include #define WAITFD_R POLLIN #define WAITFD_W POLLOUT #define WAITFD_C (POLLIN|POLLOUT) #ifndef SO_REUSEPORT #define SO_REUSEPORT SO_REUSEADDR #endif /* Some platforms use IPV6_JOIN_GROUP instead if * IPV6_ADD_MEMBERSHIP. The semantics are same, though. */ #ifndef IPV6_ADD_MEMBERSHIP #ifdef IPV6_JOIN_GROUP #define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP #endif /* IPV6_JOIN_GROUP */ #endif /* !IPV6_ADD_MEMBERSHIP */ /* Same with IPV6_DROP_MEMBERSHIP / IPV6_LEAVE_GROUP. */ #ifndef IPV6_DROP_MEMBERSHIP #ifdef IPV6_LEAVE_GROUP #define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP #endif /* IPV6_LEAVE_GROUP */ #endif /* !IPV6_DROP_MEMBERSHIP */ typedef int t_socket; typedef t_socket *p_socket; typedef struct sockaddr_storage t_sockaddr_storage; #define SOCKET_INVALID (-1) #endif /* USOCKET_H */ ================================================ FILE: src/luasocket/wsocket.c ================================================ /*=========================================================================*\ * Socket compatibilization module for Win32 * LuaSocket toolkit * * The penalty of calling select to avoid busy-wait is only paid when * the I/O call fail in the first place. \*=========================================================================*/ #include "luasocket.h" #include #include "socket.h" #include "pierror.h" /* WinSock doesn't have a strerror... */ static const char *wstrerror(int err); /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ int socket_open(void) { WSADATA wsaData; WORD wVersionRequested = MAKEWORD(2, 0); int err = WSAStartup(wVersionRequested, &wsaData ); if (err != 0) return 0; if ((LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0) && (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)) { WSACleanup(); return 0; } return 1; } /*-------------------------------------------------------------------------*\ * Close module \*-------------------------------------------------------------------------*/ int socket_close(void) { WSACleanup(); return 1; } /*-------------------------------------------------------------------------*\ * Wait for readable/writable/connected socket with timeout \*-------------------------------------------------------------------------*/ #define WAITFD_R 1 #define WAITFD_W 2 #define WAITFD_E 4 #define WAITFD_C (WAITFD_E|WAITFD_W) int socket_waitfd(p_socket ps, int sw, p_timeout tm) { int ret; fd_set rfds, wfds, efds, *rp = NULL, *wp = NULL, *ep = NULL; struct timeval tv, *tp = NULL; double t; if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */ if (sw & WAITFD_R) { FD_ZERO(&rfds); FD_SET(*ps, &rfds); rp = &rfds; } if (sw & WAITFD_W) { FD_ZERO(&wfds); FD_SET(*ps, &wfds); wp = &wfds; } if (sw & WAITFD_C) { FD_ZERO(&efds); FD_SET(*ps, &efds); ep = &efds; } if ((t = timeout_get(tm)) >= 0.0) { tv.tv_sec = (int) t; tv.tv_usec = (int) ((t-tv.tv_sec)*1.0e6); tp = &tv; } ret = select(0, rp, wp, ep, tp); if (ret == -1) return WSAGetLastError(); if (ret == 0) return IO_TIMEOUT; if (sw == WAITFD_C && FD_ISSET(*ps, &efds)) return IO_CLOSED; return IO_DONE; } /*-------------------------------------------------------------------------*\ * Select with int timeout in ms \*-------------------------------------------------------------------------*/ int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds, p_timeout tm) { struct timeval tv; double t = timeout_get(tm); tv.tv_sec = (int) t; tv.tv_usec = (int) ((t - tv.tv_sec) * 1.0e6); if (n <= 0) { Sleep((DWORD) (1000*t)); return 0; } else return select(0, rfds, wfds, efds, t >= 0.0? &tv: NULL); } /*-------------------------------------------------------------------------*\ * Close and inutilize socket \*-------------------------------------------------------------------------*/ void socket_destroy(p_socket ps) { if (*ps != SOCKET_INVALID) { socket_setblocking(ps); /* close can take a long time on WIN32 */ closesocket(*ps); *ps = SOCKET_INVALID; } } /*-------------------------------------------------------------------------*\ * \*-------------------------------------------------------------------------*/ void socket_shutdown(p_socket ps, int how) { socket_setblocking(ps); shutdown(*ps, how); socket_setnonblocking(ps); } /*-------------------------------------------------------------------------*\ * Creates and sets up a socket \*-------------------------------------------------------------------------*/ int socket_create(p_socket ps, int domain, int type, int protocol) { *ps = socket(domain, type, protocol); if (*ps != SOCKET_INVALID) return IO_DONE; else return WSAGetLastError(); } /*-------------------------------------------------------------------------*\ * Connects or returns error message \*-------------------------------------------------------------------------*/ int socket_connect(p_socket ps, SA *addr, socklen_t len, p_timeout tm) { int err; /* don't call on closed socket */ if (*ps == SOCKET_INVALID) return IO_CLOSED; /* ask system to connect */ if (connect(*ps, addr, len) == 0) return IO_DONE; /* make sure the system is trying to connect */ err = WSAGetLastError(); if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) return err; /* zero timeout case optimization */ if (timeout_iszero(tm)) return IO_TIMEOUT; /* we wait until something happens */ err = socket_waitfd(ps, WAITFD_C, tm); if (err == IO_CLOSED) { int elen = sizeof(err); /* give windows time to set the error (yes, disgusting) */ Sleep(10); /* find out why we failed */ getsockopt(*ps, SOL_SOCKET, SO_ERROR, (char *)&err, &elen); /* we KNOW there was an error. if 'why' is 0, we will return * "unknown error", but it's not really our fault */ return err > 0? err: IO_UNKNOWN; } else return err; } /*-------------------------------------------------------------------------*\ * Binds or returns error message \*-------------------------------------------------------------------------*/ int socket_bind(p_socket ps, SA *addr, socklen_t len) { int err = IO_DONE; socket_setblocking(ps); if (bind(*ps, addr, len) < 0) err = WSAGetLastError(); socket_setnonblocking(ps); return err; } /*-------------------------------------------------------------------------*\ * \*-------------------------------------------------------------------------*/ int socket_listen(p_socket ps, int backlog) { int err = IO_DONE; socket_setblocking(ps); if (listen(*ps, backlog) < 0) err = WSAGetLastError(); socket_setnonblocking(ps); return err; } /*-------------------------------------------------------------------------*\ * Accept with timeout \*-------------------------------------------------------------------------*/ int socket_accept(p_socket ps, p_socket pa, SA *addr, socklen_t *len, p_timeout tm) { if (*ps == SOCKET_INVALID) return IO_CLOSED; for ( ;; ) { int err; /* try to get client socket */ if ((*pa = accept(*ps, addr, len)) != SOCKET_INVALID) return IO_DONE; /* find out why we failed */ err = WSAGetLastError(); /* if we failed because there was no connectoin, keep trying */ if (err != WSAEWOULDBLOCK && err != WSAECONNABORTED) return err; /* call select to avoid busy wait */ if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; } } /*-------------------------------------------------------------------------*\ * Send with timeout * On windows, if you try to send 10MB, the OS will buffer EVERYTHING * this can take an awful lot of time and we will end up blocked. * Therefore, whoever calls this function should not pass a huge buffer. \*-------------------------------------------------------------------------*/ int socket_send(p_socket ps, const char *data, size_t count, size_t *sent, p_timeout tm) { int err; *sent = 0; /* avoid making system calls on closed sockets */ if (*ps == SOCKET_INVALID) return IO_CLOSED; /* loop until we send something or we give up on error */ for ( ;; ) { /* try to send something */ int put = send(*ps, data, (int) count, 0); /* if we sent something, we are done */ if (put > 0) { *sent = put; return IO_DONE; } /* deal with failure */ err = WSAGetLastError(); /* we can only proceed if there was no serious error */ if (err != WSAEWOULDBLOCK) return err; /* avoid busy wait */ if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; } } /*-------------------------------------------------------------------------*\ * Sendto with timeout \*-------------------------------------------------------------------------*/ int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent, SA *addr, socklen_t len, p_timeout tm) { int err; *sent = 0; if (*ps == SOCKET_INVALID) return IO_CLOSED; for ( ;; ) { int put = sendto(*ps, data, (int) count, 0, addr, len); if (put > 0) { *sent = put; return IO_DONE; } err = WSAGetLastError(); if (err != WSAEWOULDBLOCK) return err; if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; } } /*-------------------------------------------------------------------------*\ * Receive with timeout \*-------------------------------------------------------------------------*/ int socket_recv(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm) { int err, prev = IO_DONE; *got = 0; if (*ps == SOCKET_INVALID) return IO_CLOSED; for ( ;; ) { int taken = recv(*ps, data, (int) count, 0); if (taken > 0) { *got = taken; return IO_DONE; } if (taken == 0) return IO_CLOSED; err = WSAGetLastError(); /* On UDP, a connreset simply means the previous send failed. * So we try again. * On TCP, it means our socket is now useless, so the error passes. * (We will loop again, exiting because the same error will happen) */ if (err != WSAEWOULDBLOCK) { if (err != WSAECONNRESET || prev == WSAECONNRESET) return err; prev = err; } if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; } } /*-------------------------------------------------------------------------*\ * Recvfrom with timeout \*-------------------------------------------------------------------------*/ int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got, SA *addr, socklen_t *len, p_timeout tm) { int err, prev = IO_DONE; *got = 0; if (*ps == SOCKET_INVALID) return IO_CLOSED; for ( ;; ) { int taken = recvfrom(*ps, data, (int) count, 0, addr, len); if (taken > 0) { *got = taken; return IO_DONE; } if (taken == 0) return IO_CLOSED; err = WSAGetLastError(); /* On UDP, a connreset simply means the previous send failed. * So we try again. * On TCP, it means our socket is now useless, so the error passes. * (We will loop again, exiting because the same error will happen) */ if (err != WSAEWOULDBLOCK) { if (err != WSAECONNRESET || prev == WSAECONNRESET) return err; prev = err; } if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; } } /*-------------------------------------------------------------------------*\ * Put socket into blocking mode \*-------------------------------------------------------------------------*/ void socket_setblocking(p_socket ps) { u_long argp = 0; ioctlsocket(*ps, FIONBIO, &argp); } /*-------------------------------------------------------------------------*\ * Put socket into non-blocking mode \*-------------------------------------------------------------------------*/ void socket_setnonblocking(p_socket ps) { u_long argp = 1; ioctlsocket(*ps, FIONBIO, &argp); } /*-------------------------------------------------------------------------*\ * DNS helpers \*-------------------------------------------------------------------------*/ int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp) { *hp = gethostbyaddr(addr, len, AF_INET); if (*hp) return IO_DONE; else return WSAGetLastError(); } int socket_gethostbyname(const char *addr, struct hostent **hp) { *hp = gethostbyname(addr); if (*hp) return IO_DONE; else return WSAGetLastError(); } /*-------------------------------------------------------------------------*\ * Error translation functions \*-------------------------------------------------------------------------*/ const char *socket_hoststrerror(int err) { if (err <= 0) return io_strerror(err); switch (err) { case WSAHOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; default: return wstrerror(err); } } const char *socket_strerror(int err) { if (err <= 0) return io_strerror(err); switch (err) { case WSAEADDRINUSE: return PIE_ADDRINUSE; case WSAECONNREFUSED : return PIE_CONNREFUSED; case WSAEISCONN: return PIE_ISCONN; case WSAEACCES: return PIE_ACCESS; case WSAECONNABORTED: return PIE_CONNABORTED; case WSAECONNRESET: return PIE_CONNRESET; case WSAETIMEDOUT: return PIE_TIMEDOUT; default: return wstrerror(err); } } const char *socket_ioerror(p_socket ps, int err) { (void) ps; return socket_strerror(err); } static const char *wstrerror(int err) { switch (err) { case WSAEINTR: return "Interrupted function call"; case WSAEACCES: return PIE_ACCESS; // "Permission denied"; case WSAEFAULT: return "Bad address"; case WSAEINVAL: return "Invalid argument"; case WSAEMFILE: return "Too many open files"; case WSAEWOULDBLOCK: return "Resource temporarily unavailable"; case WSAEINPROGRESS: return "Operation now in progress"; case WSAEALREADY: return "Operation already in progress"; case WSAENOTSOCK: return "Socket operation on nonsocket"; case WSAEDESTADDRREQ: return "Destination address required"; case WSAEMSGSIZE: return "Message too long"; case WSAEPROTOTYPE: return "Protocol wrong type for socket"; case WSAENOPROTOOPT: return "Bad protocol option"; case WSAEPROTONOSUPPORT: return "Protocol not supported"; case WSAESOCKTNOSUPPORT: return PIE_SOCKTYPE; // "Socket type not supported"; case WSAEOPNOTSUPP: return "Operation not supported"; case WSAEPFNOSUPPORT: return "Protocol family not supported"; case WSAEAFNOSUPPORT: return PIE_FAMILY; // "Address family not supported by protocol family"; case WSAEADDRINUSE: return PIE_ADDRINUSE; // "Address already in use"; case WSAEADDRNOTAVAIL: return "Cannot assign requested address"; case WSAENETDOWN: return "Network is down"; case WSAENETUNREACH: return "Network is unreachable"; case WSAENETRESET: return "Network dropped connection on reset"; case WSAECONNABORTED: return "Software caused connection abort"; case WSAECONNRESET: return PIE_CONNRESET; // "Connection reset by peer"; case WSAENOBUFS: return "No buffer space available"; case WSAEISCONN: return PIE_ISCONN; // "Socket is already connected"; case WSAENOTCONN: return "Socket is not connected"; case WSAESHUTDOWN: return "Cannot send after socket shutdown"; case WSAETIMEDOUT: return PIE_TIMEDOUT; // "Connection timed out"; case WSAECONNREFUSED: return PIE_CONNREFUSED; // "Connection refused"; case WSAEHOSTDOWN: return "Host is down"; case WSAEHOSTUNREACH: return "No route to host"; case WSAEPROCLIM: return "Too many processes"; case WSASYSNOTREADY: return "Network subsystem is unavailable"; case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range"; case WSANOTINITIALISED: return "Successful WSAStartup not yet performed"; case WSAEDISCON: return "Graceful shutdown in progress"; case WSAHOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; // "Host not found"; case WSATRY_AGAIN: return "Nonauthoritative host not found"; case WSANO_RECOVERY: return PIE_FAIL; // "Nonrecoverable name lookup error"; case WSANO_DATA: return "Valid name, no data record of requested type"; default: return "Unknown error"; } } const char *socket_gaistrerror(int err) { if (err == 0) return NULL; switch (err) { case EAI_AGAIN: return PIE_AGAIN; case EAI_BADFLAGS: return PIE_BADFLAGS; #ifdef EAI_BADHINTS case EAI_BADHINTS: return PIE_BADHINTS; #endif case EAI_FAIL: return PIE_FAIL; case EAI_FAMILY: return PIE_FAMILY; case EAI_MEMORY: return PIE_MEMORY; case EAI_NONAME: return PIE_NONAME; #ifdef EAI_OVERFLOW case EAI_OVERFLOW: return PIE_OVERFLOW; #endif #ifdef EAI_PROTOCOL case EAI_PROTOCOL: return PIE_PROTOCOL; #endif case EAI_SERVICE: return PIE_SERVICE; case EAI_SOCKTYPE: return PIE_SOCKTYPE; #ifdef EAI_SYSTEM case EAI_SYSTEM: return strerror(errno); #endif default: return gai_strerror(err); } } ================================================ FILE: src/luasocket/wsocket.h ================================================ #ifndef WSOCKET_H #define WSOCKET_H /*=========================================================================*\ * Socket compatibilization module for Win32 * LuaSocket toolkit \*=========================================================================*/ /*=========================================================================*\ * WinSock include files \*=========================================================================*/ #include #include typedef int socklen_t; typedef SOCKADDR_STORAGE t_sockaddr_storage; typedef SOCKET t_socket; typedef t_socket *p_socket; #ifndef IPV6_V6ONLY #define IPV6_V6ONLY 27 #endif #define SOCKET_INVALID (INVALID_SOCKET) #ifndef SO_REUSEPORT #define SO_REUSEPORT SO_REUSEADDR #endif #ifndef AI_NUMERICSERV #define AI_NUMERICSERV (0) #endif #endif /* WSOCKET_H */ ================================================ FILE: src/lundump.c ================================================ /* ** $Id: lundump.c,v 2.7.1.4 2008/04/04 19:51:41 roberto Exp $ ** load precompiled Lua chunks ** See Copyright Notice in lua.h */ #include #define lundump_c #define LUA_CORE #include "lua.h" #include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "lmem.h" #include "lobject.h" #include "lstring.h" #include "lundump.h" #include "lzio.h" typedef struct { lua_State* L; ZIO* Z; Mbuffer* b; const char* name; } LoadState; #ifdef LUAC_TRUST_BINARIES #define IF(c,s) #define error(S,s) #else #define IF(c,s) if (c) error(S,s) static void error(LoadState* S, const char* why) { luaO_pushfstring(S->L,"%s: %s in precompiled chunk",S->name,why); luaD_throw(S->L,LUA_ERRSYNTAX); } #endif #define LoadMem(S,b,n,size) LoadBlock(S,b,(n)*(size)) #define LoadByte(S) (lu_byte)LoadChar(S) #define LoadVar(S,x) LoadMem(S,&x,1,sizeof(x)) #define LoadVector(S,b,n,size) LoadMem(S,b,n,size) static void LoadBlock(LoadState* S, void* b, size_t size) { size_t r=luaZ_read(S->Z,b,size); IF (r!=0, "unexpected end"); } static int LoadChar(LoadState* S) { char x; LoadVar(S,x); return x; } static int LoadInt(LoadState* S) { int x; LoadVar(S,x); IF (x<0, "bad integer"); return x; } static lua_Number LoadNumber(LoadState* S) { lua_Number x; LoadVar(S,x); return x; } static TString* LoadString(LoadState* S) { size_t size; LoadVar(S,size); if (size==0) return NULL; else { char* s=luaZ_openspace(S->L,S->b,size); LoadBlock(S,s,size); return luaS_newlstr(S->L,s,size-1); /* remove trailing '\0' */ } } static void LoadCode(LoadState* S, Proto* f) { int n=LoadInt(S); f->code=luaM_newvector(S->L,n,Instruction); f->sizecode=n; LoadVector(S,f->code,n,sizeof(Instruction)); } static Proto* LoadFunction(LoadState* S, TString* p); static void LoadConstants(LoadState* S, Proto* f) { int i,n; n=LoadInt(S); f->k=luaM_newvector(S->L,n,TValue); f->sizek=n; for (i=0; ik[i]); for (i=0; ik[i]; int t=LoadChar(S); switch (t) { case LUA_TNIL: setnilvalue(o); break; case LUA_TBOOLEAN: setbvalue(o,LoadChar(S)!=0); break; case LUA_TNUMBER: setnvalue(o,LoadNumber(S)); break; case LUA_TSTRING: setsvalue2n(S->L,o,LoadString(S)); break; default: error(S,"bad constant"); break; } } n=LoadInt(S); f->p=luaM_newvector(S->L,n,Proto*); f->sizep=n; for (i=0; ip[i]=NULL; for (i=0; ip[i]=LoadFunction(S,f->source); } static void LoadDebug(LoadState* S, Proto* f) { int i,n; n=LoadInt(S); f->lineinfo=luaM_newvector(S->L,n,int); f->sizelineinfo=n; LoadVector(S,f->lineinfo,n,sizeof(int)); n=LoadInt(S); f->locvars=luaM_newvector(S->L,n,LocVar); f->sizelocvars=n; for (i=0; ilocvars[i].varname=NULL; for (i=0; ilocvars[i].varname=LoadString(S); f->locvars[i].startpc=LoadInt(S); f->locvars[i].endpc=LoadInt(S); } n=LoadInt(S); f->upvalues=luaM_newvector(S->L,n,TString*); f->sizeupvalues=n; for (i=0; iupvalues[i]=NULL; for (i=0; iupvalues[i]=LoadString(S); } static Proto* LoadFunction(LoadState* S, TString* p) { Proto* f; if (++S->L->nCcalls > LUAI_MAXCCALLS) error(S,"code too deep"); f=luaF_newproto(S->L); setptvalue2s(S->L,S->L->top,f); incr_top(S->L); f->source=LoadString(S); if (f->source==NULL) f->source=p; f->linedefined=LoadInt(S); f->lastlinedefined=LoadInt(S); f->nups=LoadByte(S); f->numparams=LoadByte(S); f->is_vararg=LoadByte(S); f->maxstacksize=LoadByte(S); LoadCode(S,f); LoadConstants(S,f); LoadDebug(S,f); IF (!luaG_checkcode(f), "bad code"); S->L->top--; S->L->nCcalls--; return f; } static void LoadHeader(LoadState* S) { char h[LUAC_HEADERSIZE]; char s[LUAC_HEADERSIZE]; luaU_header(h); LoadBlock(S,s,LUAC_HEADERSIZE); IF (memcmp(h,s,LUAC_HEADERSIZE)!=0, "bad header"); } /* ** load precompiled chunk */ Proto* luaU_undump (lua_State* L, ZIO* Z, Mbuffer* buff, const char* name) { LoadState S; if (*name=='@' || *name=='=') S.name=name+1; else if (*name==LUA_SIGNATURE[0]) S.name="binary string"; else S.name=name; S.L=L; S.Z=Z; S.b=buff; LoadHeader(&S); return LoadFunction(&S,luaS_newliteral(L,"=?")); } /* * make header */ void luaU_header (char* h) { int x=1; memcpy(h,LUA_SIGNATURE,sizeof(LUA_SIGNATURE)-1); h+=sizeof(LUA_SIGNATURE)-1; *h++=(char)LUAC_VERSION; *h++=(char)LUAC_FORMAT; *h++=(char)*(char*)&x; /* endianness */ *h++=(char)sizeof(int); *h++=(char)sizeof(size_t); *h++=(char)sizeof(Instruction); *h++=(char)sizeof(lua_Number); *h++=(char)(((lua_Number)0.5)==0); /* is lua_Number integral? */ } ================================================ FILE: src/lundump.h ================================================ /* ** $Id: lundump.h,v 1.37.1.1 2007/12/27 13:02:25 roberto Exp $ ** load precompiled Lua chunks ** See Copyright Notice in lua.h */ #ifndef lundump_h #define lundump_h #include "lobject.h" #include "lzio.h" /* load one chunk; from lundump.c */ LUAI_FUNC Proto* luaU_undump (lua_State* L, ZIO* Z, Mbuffer* buff, const char* name); /* make header; from lundump.c */ LUAI_FUNC void luaU_header (char* h); /* dump one chunk; from ldump.c */ LUAI_FUNC int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, void* data, int strip); #ifdef luac_c /* print one chunk; from print.c */ LUAI_FUNC void luaU_print (const Proto* f, int full); #endif /* for header of binary files -- this is Lua 5.1 */ #define LUAC_VERSION 0x51 /* for header of binary files -- this is the official format */ #define LUAC_FORMAT 0 /* size of header of binary files */ #define LUAC_HEADERSIZE 12 #endif ================================================ FILE: src/lvm.c ================================================ /* ** $Id: lvm.c,v 2.63.1.5 2011/08/17 20:43:11 roberto Exp $ ** Lua virtual machine ** See Copyright Notice in lua.h */ #include #include #include #define lvm_c #define LUA_CORE #include "lua.h" #include "ldebug.h" #include "ldo.h" #include "lfunc.h" #include "lgc.h" #include "lobject.h" #include "lopcodes.h" #include "lstate.h" #include "lstring.h" #include "ltable.h" #include "ltm.h" #include "lvm.h" /* limit for table tag-method chains (to avoid loops) */ #define MAXTAGLOOP 100 const TValue *luaV_tonumber (const TValue *obj, TValue *n) { lua_Number num; if (ttisnumber(obj)) return obj; if (ttisstring(obj) && luaO_str2d(svalue(obj), &num)) { setnvalue(n, num); return n; } else return NULL; } int luaV_tostring (lua_State *L, StkId obj) { if (!ttisnumber(obj)) return 0; else { char s[LUAI_MAXNUMBER2STR]; lua_Number n = nvalue(obj); lua_number2str(s, n); setsvalue2s(L, obj, luaS_new(L, s)); return 1; } } static void traceexec (lua_State *L, const Instruction *pc) { lu_byte mask = L->hookmask; const Instruction *oldpc = L->savedpc; L->savedpc = pc; if ((mask & LUA_MASKCOUNT) && L->hookcount == 0) { resethookcount(L); luaD_callhook(L, LUA_HOOKCOUNT, -1); } if (mask & LUA_MASKLINE) { Proto *p = ci_func(L->ci)->l.p; int npc = pcRel(pc, p); int newline = getline(p, npc); /* call linehook when enter a new function, when jump back (loop), or when enter a new line */ if (npc == 0 || pc <= oldpc || newline != getline(p, pcRel(oldpc, p))) luaD_callhook(L, LUA_HOOKLINE, newline); } } static void callTMres (lua_State *L, StkId res, const TValue *f, const TValue *p1, const TValue *p2) { ptrdiff_t result = savestack(L, res); setobj2s(L, L->top, f); /* push function */ setobj2s(L, L->top+1, p1); /* 1st argument */ setobj2s(L, L->top+2, p2); /* 2nd argument */ luaD_checkstack(L, 3); L->top += 3; luaD_call(L, L->top - 3, 1); res = restorestack(L, result); L->top--; setobjs2s(L, res, L->top); } static void callTM (lua_State *L, const TValue *f, const TValue *p1, const TValue *p2, const TValue *p3) { setobj2s(L, L->top, f); /* push function */ setobj2s(L, L->top+1, p1); /* 1st argument */ setobj2s(L, L->top+2, p2); /* 2nd argument */ setobj2s(L, L->top+3, p3); /* 3th argument */ luaD_checkstack(L, 4); L->top += 4; luaD_call(L, L->top - 4, 0); } void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) { int loop; for (loop = 0; loop < MAXTAGLOOP; loop++) { const TValue *tm; if (ttistable(t)) { /* `t' is a table? */ Table *h = hvalue(t); const TValue *res = luaH_get(h, key); /* do a primitive get */ if (!ttisnil(res) || /* result is no nil? */ (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) { /* or no TM? */ setobj2s(L, val, res); return; } /* else will try the tag method */ } else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX))) luaG_typeerror(L, t, "index"); if (ttisfunction(tm)) { callTMres(L, val, tm, t, key); return; } t = tm; /* else repeat with `tm' */ } luaG_runerror(L, "loop in gettable"); } void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) { int loop; TValue temp; for (loop = 0; loop < MAXTAGLOOP; loop++) { const TValue *tm; if (ttistable(t)) { /* `t' is a table? */ Table *h = hvalue(t); TValue *oldval = luaH_set(L, h, key); /* do a primitive set */ if (!ttisnil(oldval) || /* result is no nil? */ (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) { /* or no TM? */ setobj2t(L, oldval, val); h->flags = 0; luaC_barriert(L, h, val); return; } /* else will try the tag method */ } else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX))) luaG_typeerror(L, t, "index"); if (ttisfunction(tm)) { callTM(L, tm, t, key, val); return; } /* else repeat with `tm' */ setobj(L, &temp, tm); /* avoid pointing inside table (may rehash) */ t = &temp; } luaG_runerror(L, "loop in settable"); } static int call_binTM (lua_State *L, const TValue *p1, const TValue *p2, StkId res, TMS event) { const TValue *tm = luaT_gettmbyobj(L, p1, event); /* try first operand */ if (ttisnil(tm)) tm = luaT_gettmbyobj(L, p2, event); /* try second operand */ if (ttisnil(tm)) return 0; callTMres(L, res, tm, p1, p2); return 1; } static const TValue *get_compTM (lua_State *L, Table *mt1, Table *mt2, TMS event) { const TValue *tm1 = fasttm(L, mt1, event); const TValue *tm2; if (tm1 == NULL) return NULL; /* no metamethod */ if (mt1 == mt2) return tm1; /* same metatables => same metamethods */ tm2 = fasttm(L, mt2, event); if (tm2 == NULL) return NULL; /* no metamethod */ if (luaO_rawequalObj(tm1, tm2)) /* same metamethods? */ return tm1; return NULL; } static int call_orderTM (lua_State *L, const TValue *p1, const TValue *p2, TMS event) { const TValue *tm1 = luaT_gettmbyobj(L, p1, event); const TValue *tm2; if (ttisnil(tm1)) return -1; /* no metamethod? */ tm2 = luaT_gettmbyobj(L, p2, event); if (!luaO_rawequalObj(tm1, tm2)) /* different metamethods? */ return -1; callTMres(L, L->top, tm1, p1, p2); return !l_isfalse(L->top); } static int l_strcmp (const TString *ls, const TString *rs) { const char *l = getstr(ls); size_t ll = ls->tsv.len; const char *r = getstr(rs); size_t lr = rs->tsv.len; for (;;) { int temp = strcoll(l, r); if (temp != 0) return temp; else { /* strings are equal up to a `\0' */ size_t len = strlen(l); /* index of first `\0' in both strings */ if (len == lr) /* r is finished? */ return (len == ll) ? 0 : 1; else if (len == ll) /* l is finished? */ return -1; /* l is smaller than r (because r is not finished) */ /* both strings longer than `len'; go on comparing (after the `\0') */ len++; l += len; ll -= len; r += len; lr -= len; } } } int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r) { int res; if (ttype(l) != ttype(r)) return luaG_ordererror(L, l, r); else if (ttisnumber(l)) return luai_numlt(nvalue(l), nvalue(r)); else if (ttisstring(l)) return l_strcmp(rawtsvalue(l), rawtsvalue(r)) < 0; else if ((res = call_orderTM(L, l, r, TM_LT)) != -1) return res; return luaG_ordererror(L, l, r); } static int lessequal (lua_State *L, const TValue *l, const TValue *r) { int res; if (ttype(l) != ttype(r)) return luaG_ordererror(L, l, r); else if (ttisnumber(l)) return luai_numle(nvalue(l), nvalue(r)); else if (ttisstring(l)) return l_strcmp(rawtsvalue(l), rawtsvalue(r)) <= 0; else if ((res = call_orderTM(L, l, r, TM_LE)) != -1) /* first try `le' */ return res; else if ((res = call_orderTM(L, r, l, TM_LT)) != -1) /* else try `lt' */ return !res; return luaG_ordererror(L, l, r); } int luaV_equalval (lua_State *L, const TValue *t1, const TValue *t2) { const TValue *tm; lua_assert(ttype(t1) == ttype(t2)); switch (ttype(t1)) { case LUA_TNIL: return 1; case LUA_TNUMBER: return luai_numeq(nvalue(t1), nvalue(t2)); case LUA_TBOOLEAN: return bvalue(t1) == bvalue(t2); /* true must be 1 !! */ case LUA_TLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); case LUA_TUSERDATA: { if (uvalue(t1) == uvalue(t2)) return 1; tm = get_compTM(L, uvalue(t1)->metatable, uvalue(t2)->metatable, TM_EQ); break; /* will try TM */ } case LUA_TTABLE: { if (hvalue(t1) == hvalue(t2)) return 1; tm = get_compTM(L, hvalue(t1)->metatable, hvalue(t2)->metatable, TM_EQ); break; /* will try TM */ } default: return gcvalue(t1) == gcvalue(t2); } if (tm == NULL) return 0; /* no TM? */ callTMres(L, L->top, tm, t1, t2); /* call TM */ return !l_isfalse(L->top); } void luaV_concat (lua_State *L, int total, int last) { do { StkId top = L->base + last + 1; int n = 2; /* number of elements handled in this pass (at least 2) */ if (!(ttisstring(top-2) || ttisnumber(top-2)) || !tostring(L, top-1)) { if (!call_binTM(L, top-2, top-1, top-2, TM_CONCAT)) luaG_concaterror(L, top-2, top-1); } else if (tsvalue(top-1)->len == 0) /* second op is empty? */ (void)tostring(L, top - 2); /* result is first op (as string) */ else { /* at least two string values; get as many as possible */ size_t tl = tsvalue(top-1)->len; char *buffer; int i; /* collect total length */ for (n = 1; n < total && tostring(L, top-n-1); n++) { size_t l = tsvalue(top-n-1)->len; if (l >= MAX_SIZET - tl) luaG_runerror(L, "string length overflow"); tl += l; } buffer = luaZ_openspace(L, &G(L)->buff, tl); tl = 0; for (i=n; i>0; i--) { /* concat all strings */ size_t l = tsvalue(top-i)->len; memcpy(buffer+tl, svalue(top-i), l); tl += l; } setsvalue2s(L, top-n, luaS_newlstr(L, buffer, tl)); } total -= n-1; /* got `n' strings to create 1 new */ last -= n-1; } while (total > 1); /* repeat until only 1 result left */ } static void Arith (lua_State *L, StkId ra, const TValue *rb, const TValue *rc, TMS op) { TValue tempb, tempc; const TValue *b, *c; if ((b = luaV_tonumber(rb, &tempb)) != NULL && (c = luaV_tonumber(rc, &tempc)) != NULL) { lua_Number nb = nvalue(b), nc = nvalue(c); switch (op) { case TM_ADD: setnvalue(ra, luai_numadd(nb, nc)); break; case TM_SUB: setnvalue(ra, luai_numsub(nb, nc)); break; case TM_MUL: setnvalue(ra, luai_nummul(nb, nc)); break; case TM_DIV: setnvalue(ra, luai_numdiv(nb, nc)); break; case TM_MOD: setnvalue(ra, luai_nummod(nb, nc)); break; case TM_POW: setnvalue(ra, luai_numpow(nb, nc)); break; case TM_UNM: setnvalue(ra, luai_numunm(nb)); break; default: lua_assert(0); break; } } else if (!call_binTM(L, rb, rc, ra, op)) luaG_aritherror(L, rb, rc); } /* ** some macros for common tasks in `luaV_execute' */ #define runtime_check(L, c) { if (!(c)) break; } #define RA(i) (base+GETARG_A(i)) /* to be used after possible stack reallocation */ #define RB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgR, base+GETARG_B(i)) #define RC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgR, base+GETARG_C(i)) #define RKB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, \ ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i)) #define RKC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgK, \ ISK(GETARG_C(i)) ? k+INDEXK(GETARG_C(i)) : base+GETARG_C(i)) #define KBx(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, k+GETARG_Bx(i)) #define dojump(L,pc,i) {(pc) += (i); luai_threadyield(L);} #define Protect(x) { L->savedpc = pc; {x;}; base = L->base; } #define arith_op(op,tm) { \ TValue *rb = RKB(i); \ TValue *rc = RKC(i); \ if (ttisnumber(rb) && ttisnumber(rc)) { \ lua_Number nb = nvalue(rb), nc = nvalue(rc); \ setnvalue(ra, op(nb, nc)); \ } \ else \ Protect(Arith(L, ra, rb, rc, tm)); \ } void luaV_execute (lua_State *L, int nexeccalls) { LClosure *cl; StkId base; TValue *k; const Instruction *pc; reentry: /* entry point */ lua_assert(isLua(L->ci)); pc = L->savedpc; cl = &clvalue(L->ci->func)->l; base = L->base; k = cl->p->k; /* main loop of interpreter */ for (;;) { const Instruction i = *pc++; StkId ra; if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) && (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)) { traceexec(L, pc); if (L->status == LUA_YIELD) { /* did hook yield? */ L->savedpc = pc - 1; return; } base = L->base; } /* warning!! several calls may realloc the stack and invalidate `ra' */ ra = RA(i); lua_assert(base == L->base && L->base == L->ci->base); lua_assert(base <= L->top && L->top <= L->stack + L->stacksize); lua_assert(L->top == L->ci->top || luaG_checkopenop(i)); switch (GET_OPCODE(i)) { case OP_MOVE: { setobjs2s(L, ra, RB(i)); continue; } case OP_LOADK: { setobj2s(L, ra, KBx(i)); continue; } case OP_LOADBOOL: { setbvalue(ra, GETARG_B(i)); if (GETARG_C(i)) pc++; /* skip next instruction (if C) */ continue; } case OP_LOADNIL: { TValue *rb = RB(i); do { setnilvalue(rb--); } while (rb >= ra); continue; } case OP_GETUPVAL: { int b = GETARG_B(i); setobj2s(L, ra, cl->upvals[b]->v); continue; } case OP_GETGLOBAL: { TValue g; TValue *rb = KBx(i); sethvalue(L, &g, cl->env); lua_assert(ttisstring(rb)); Protect(luaV_gettable(L, &g, rb, ra)); continue; } case OP_GETTABLE: { Protect(luaV_gettable(L, RB(i), RKC(i), ra)); continue; } case OP_SETGLOBAL: { TValue g; sethvalue(L, &g, cl->env); lua_assert(ttisstring(KBx(i))); Protect(luaV_settable(L, &g, KBx(i), ra)); continue; } case OP_SETUPVAL: { UpVal *uv = cl->upvals[GETARG_B(i)]; setobj(L, uv->v, ra); luaC_barrier(L, uv, ra); continue; } case OP_SETTABLE: { Protect(luaV_settable(L, ra, RKB(i), RKC(i))); continue; } case OP_NEWTABLE: { int b = GETARG_B(i); int c = GETARG_C(i); sethvalue(L, ra, luaH_new(L, luaO_fb2int(b), luaO_fb2int(c))); Protect(luaC_checkGC(L)); continue; } case OP_SELF: { StkId rb = RB(i); setobjs2s(L, ra+1, rb); Protect(luaV_gettable(L, rb, RKC(i), ra)); continue; } case OP_ADD: { arith_op(luai_numadd, TM_ADD); continue; } case OP_SUB: { arith_op(luai_numsub, TM_SUB); continue; } case OP_MUL: { arith_op(luai_nummul, TM_MUL); continue; } case OP_DIV: { arith_op(luai_numdiv, TM_DIV); continue; } case OP_MOD: { arith_op(luai_nummod, TM_MOD); continue; } case OP_POW: { arith_op(luai_numpow, TM_POW); continue; } case OP_UNM: { TValue *rb = RB(i); if (ttisnumber(rb)) { lua_Number nb = nvalue(rb); setnvalue(ra, luai_numunm(nb)); } else { Protect(Arith(L, ra, rb, rb, TM_UNM)); } continue; } case OP_NOT: { int res = l_isfalse(RB(i)); /* next assignment may change this value */ setbvalue(ra, res); continue; } case OP_LEN: { const TValue *rb = RB(i); switch (ttype(rb)) { case LUA_TTABLE: { setnvalue(ra, cast_num(luaH_getn(hvalue(rb)))); break; } case LUA_TSTRING: { setnvalue(ra, cast_num(tsvalue(rb)->len)); break; } default: { /* try metamethod */ Protect( if (!call_binTM(L, rb, luaO_nilobject, ra, TM_LEN)) luaG_typeerror(L, rb, "get length of"); ) } } continue; } case OP_CONCAT: { int b = GETARG_B(i); int c = GETARG_C(i); Protect(luaV_concat(L, c-b+1, c); luaC_checkGC(L)); setobjs2s(L, RA(i), base+b); continue; } case OP_JMP: { dojump(L, pc, GETARG_sBx(i)); continue; } case OP_EQ: { TValue *rb = RKB(i); TValue *rc = RKC(i); Protect( if (equalobj(L, rb, rc) == GETARG_A(i)) dojump(L, pc, GETARG_sBx(*pc)); ) pc++; continue; } case OP_LT: { Protect( if (luaV_lessthan(L, RKB(i), RKC(i)) == GETARG_A(i)) dojump(L, pc, GETARG_sBx(*pc)); ) pc++; continue; } case OP_LE: { Protect( if (lessequal(L, RKB(i), RKC(i)) == GETARG_A(i)) dojump(L, pc, GETARG_sBx(*pc)); ) pc++; continue; } case OP_TEST: { if (l_isfalse(ra) != GETARG_C(i)) dojump(L, pc, GETARG_sBx(*pc)); pc++; continue; } case OP_TESTSET: { TValue *rb = RB(i); if (l_isfalse(rb) != GETARG_C(i)) { setobjs2s(L, ra, rb); dojump(L, pc, GETARG_sBx(*pc)); } pc++; continue; } case OP_CALL: { int b = GETARG_B(i); int nresults = GETARG_C(i) - 1; if (b != 0) L->top = ra+b; /* else previous instruction set top */ L->savedpc = pc; switch (luaD_precall(L, ra, nresults)) { case PCRLUA: { nexeccalls++; goto reentry; /* restart luaV_execute over new Lua function */ } case PCRC: { /* it was a C function (`precall' called it); adjust results */ if (nresults >= 0) L->top = L->ci->top; base = L->base; continue; } default: { return; /* yield */ } } } case OP_TAILCALL: { int b = GETARG_B(i); if (b != 0) L->top = ra+b; /* else previous instruction set top */ L->savedpc = pc; lua_assert(GETARG_C(i) - 1 == LUA_MULTRET); switch (luaD_precall(L, ra, LUA_MULTRET)) { case PCRLUA: { /* tail call: put new frame in place of previous one */ CallInfo *ci = L->ci - 1; /* previous frame */ int aux; StkId func = ci->func; StkId pfunc = (ci+1)->func; /* previous function index */ if (L->openupval) luaF_close(L, ci->base); L->base = ci->base = ci->func + ((ci+1)->base - pfunc); for (aux = 0; pfunc+aux < L->top; aux++) /* move frame down */ setobjs2s(L, func+aux, pfunc+aux); ci->top = L->top = func+aux; /* correct top */ lua_assert(L->top == L->base + clvalue(func)->l.p->maxstacksize); ci->savedpc = L->savedpc; ci->tailcalls++; /* one more call lost */ L->ci--; /* remove new frame */ goto reentry; } case PCRC: { /* it was a C function (`precall' called it) */ base = L->base; continue; } default: { return; /* yield */ } } } case OP_RETURN: { int b = GETARG_B(i); if (b != 0) L->top = ra+b-1; if (L->openupval) luaF_close(L, base); L->savedpc = pc; b = luaD_poscall(L, ra); if (--nexeccalls == 0) /* was previous function running `here'? */ return; /* no: return */ else { /* yes: continue its execution */ if (b) L->top = L->ci->top; lua_assert(isLua(L->ci)); lua_assert(GET_OPCODE(*((L->ci)->savedpc - 1)) == OP_CALL); goto reentry; } } case OP_FORLOOP: { lua_Number step = nvalue(ra+2); lua_Number idx = luai_numadd(nvalue(ra), step); /* increment index */ lua_Number limit = nvalue(ra+1); if (luai_numlt(0, step) ? luai_numle(idx, limit) : luai_numle(limit, idx)) { dojump(L, pc, GETARG_sBx(i)); /* jump back */ setnvalue(ra, idx); /* update internal index... */ setnvalue(ra+3, idx); /* ...and external index */ } continue; } case OP_FORPREP: { const TValue *init = ra; const TValue *plimit = ra+1; const TValue *pstep = ra+2; L->savedpc = pc; /* next steps may throw errors */ if (!tonumber(init, ra)) luaG_runerror(L, LUA_QL("for") " initial value must be a number"); else if (!tonumber(plimit, ra+1)) luaG_runerror(L, LUA_QL("for") " limit must be a number"); else if (!tonumber(pstep, ra+2)) luaG_runerror(L, LUA_QL("for") " step must be a number"); setnvalue(ra, luai_numsub(nvalue(ra), nvalue(pstep))); dojump(L, pc, GETARG_sBx(i)); continue; } case OP_TFORLOOP: { StkId cb = ra + 3; /* call base */ setobjs2s(L, cb+2, ra+2); setobjs2s(L, cb+1, ra+1); setobjs2s(L, cb, ra); L->top = cb+3; /* func. + 2 args (state and index) */ Protect(luaD_call(L, cb, GETARG_C(i))); L->top = L->ci->top; cb = RA(i) + 3; /* previous call may change the stack */ if (!ttisnil(cb)) { /* continue loop? */ setobjs2s(L, cb-1, cb); /* save control variable */ dojump(L, pc, GETARG_sBx(*pc)); /* jump back */ } pc++; continue; } case OP_SETLIST: { int n = GETARG_B(i); int c = GETARG_C(i); int last; Table *h; if (n == 0) { n = cast_int(L->top - ra) - 1; L->top = L->ci->top; } if (c == 0) c = cast_int(*pc++); runtime_check(L, ttistable(ra)); h = hvalue(ra); last = ((c-1)*LFIELDS_PER_FLUSH) + n; if (last > h->sizearray) /* needs more space? */ luaH_resizearray(L, h, last); /* pre-alloc it at once */ for (; n > 0; n--) { TValue *val = ra+n; setobj2t(L, luaH_setnum(L, h, last--), val); luaC_barriert(L, h, val); } continue; } case OP_CLOSE: { luaF_close(L, ra); continue; } case OP_CLOSURE: { Proto *p; Closure *ncl; int nup, j; p = cl->p->p[GETARG_Bx(i)]; nup = p->nups; ncl = luaF_newLclosure(L, nup, cl->env); ncl->l.p = p; for (j=0; jl.upvals[j] = cl->upvals[GETARG_B(*pc)]; else { lua_assert(GET_OPCODE(*pc) == OP_MOVE); ncl->l.upvals[j] = luaF_findupval(L, base + GETARG_B(*pc)); } } setclvalue(L, ra, ncl); Protect(luaC_checkGC(L)); continue; } case OP_VARARG: { int b = GETARG_B(i) - 1; int j; CallInfo *ci = L->ci; int n = cast_int(ci->base - ci->func) - cl->p->numparams - 1; if (b == LUA_MULTRET) { Protect(luaD_checkstack(L, n)); ra = RA(i); /* previous call may change the stack */ b = n; L->top = ra + n; } for (j = 0; j < b; j++) { if (j < n) { setobjs2s(L, ra + j, ci->base - n + j); } else { setnilvalue(ra + j); } } continue; } } } } ================================================ FILE: src/lvm.h ================================================ /* ** $Id: lvm.h,v 2.5.1.1 2007/12/27 13:02:25 roberto Exp $ ** Lua virtual machine ** See Copyright Notice in lua.h */ #ifndef lvm_h #define lvm_h #include "ldo.h" #include "lobject.h" #include "ltm.h" #define tostring(L,o) ((ttype(o) == LUA_TSTRING) || (luaV_tostring(L, o))) #define tonumber(o,n) (ttype(o) == LUA_TNUMBER || \ (((o) = luaV_tonumber(o,n)) != NULL)) #define equalobj(L,o1,o2) \ (ttype(o1) == ttype(o2) && luaV_equalval(L, o1, o2)) LUAI_FUNC int luaV_lessthan (lua_State *L, const TValue *l, const TValue *r); LUAI_FUNC int luaV_equalval (lua_State *L, const TValue *t1, const TValue *t2); LUAI_FUNC const TValue *luaV_tonumber (const TValue *obj, TValue *n); LUAI_FUNC int luaV_tostring (lua_State *L, StkId obj); LUAI_FUNC void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val); LUAI_FUNC void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val); LUAI_FUNC void luaV_execute (lua_State *L, int nexeccalls); LUAI_FUNC void luaV_concat (lua_State *L, int total, int last); #endif ================================================ FILE: src/lzio.c ================================================ /* ** $Id: lzio.c,v 1.31.1.1 2007/12/27 13:02:25 roberto Exp $ ** a generic input stream interface ** See Copyright Notice in lua.h */ #include #define lzio_c #define LUA_CORE #include "lua.h" #include "llimits.h" #include "lmem.h" #include "lstate.h" #include "lzio.h" int luaZ_fill (ZIO *z) { size_t size; lua_State *L = z->L; const char *buff; lua_unlock(L); buff = z->reader(L, z->data, &size); lua_lock(L); if (buff == NULL || size == 0) return EOZ; z->n = size - 1; z->p = buff; return char2int(*(z->p++)); } int luaZ_lookahead (ZIO *z) { if (z->n == 0) { if (luaZ_fill(z) == EOZ) return EOZ; else { z->n++; /* luaZ_fill removed first byte; put back it */ z->p--; } } return char2int(*z->p); } void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, void *data) { z->L = L; z->reader = reader; z->data = data; z->n = 0; z->p = NULL; } /* --------------------------------------------------------------- read --- */ size_t luaZ_read (ZIO *z, void *b, size_t n) { while (n) { size_t m; if (luaZ_lookahead(z) == EOZ) return n; /* return number of missing bytes */ m = (n <= z->n) ? n : z->n; /* min. between n and z->n */ memcpy(b, z->p, m); z->n -= m; z->p += m; b = (char *)b + m; n -= m; } return 0; } /* ------------------------------------------------------------------------ */ char *luaZ_openspace (lua_State *L, Mbuffer *buff, size_t n) { if (n > buff->buffsize) { if (n < LUA_MINBUFFER) n = LUA_MINBUFFER; luaZ_resizebuffer(L, buff, n); } return buff->buffer; } ================================================ FILE: src/lzio.h ================================================ /* ** $Id: lzio.h,v 1.21.1.1 2007/12/27 13:02:25 roberto Exp $ ** Buffered streams ** See Copyright Notice in lua.h */ #ifndef lzio_h #define lzio_h #include "lua.h" #include "lmem.h" #define EOZ (-1) /* end of stream */ typedef struct Zio ZIO; #define char2int(c) cast(int, cast(unsigned char, (c))) #define zgetc(z) (((z)->n--)>0 ? char2int(*(z)->p++) : luaZ_fill(z)) typedef struct Mbuffer { char *buffer; size_t n; size_t buffsize; } Mbuffer; #define luaZ_initbuffer(L, buff) ((buff)->buffer = NULL, (buff)->buffsize = 0) #define luaZ_buffer(buff) ((buff)->buffer) #define luaZ_sizebuffer(buff) ((buff)->buffsize) #define luaZ_bufflen(buff) ((buff)->n) #define luaZ_resetbuffer(buff) ((buff)->n = 0) #define luaZ_resizebuffer(L, buff, size) \ (luaM_reallocvector(L, (buff)->buffer, (buff)->buffsize, size, char), \ (buff)->buffsize = size) #define luaZ_freebuffer(L, buff) luaZ_resizebuffer(L, buff, 0) LUAI_FUNC char *luaZ_openspace (lua_State *L, Mbuffer *buff, size_t n); LUAI_FUNC void luaZ_init (lua_State *L, ZIO *z, lua_Reader reader, void *data); LUAI_FUNC size_t luaZ_read (ZIO* z, void* b, size_t n); /* read next n bytes */ LUAI_FUNC int luaZ_lookahead (ZIO *z); /* --------- Private Part ------------------ */ struct Zio { size_t n; /* bytes still unread */ const char *p; /* current position in buffer */ lua_Reader reader; void* data; /* additional data */ lua_State *L; /* Lua state (for reader) */ }; LUAI_FUNC int luaZ_fill (ZIO *z); #endif ================================================ FILE: src/realpath.c ================================================ /* * Copyright (c) 2003 Constantin S. Svintsoff * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The names of the authors may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #if !__APPLE__ extern size_t strlcpy(char * dst, const char * src, size_t size); extern size_t strlcat(char * dst, const char * src, size_t size); #endif /* * Find the real name of path, by removing all ".", ".." and symlink * components. Returns (resolved) on success, or (NULL) on failure, * in which case the path which caused trouble is left in (resolved). */ char * realpath(const char * __restrict path, char * __restrict resolved) { struct stat sb; char *p, *q, *s; size_t left_len, resolved_len; unsigned symlinks; int m, slen; char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX]; if (path == NULL) { errno = EINVAL; return (NULL); } if (path[0] == '\0') { errno = ENOENT; return (NULL); } if (resolved == NULL) { resolved = malloc(PATH_MAX); if (resolved == NULL) return (NULL); m = 1; } else m = 0; symlinks = 0; if (path[0] == '/') { resolved[0] = '/'; resolved[1] = '\0'; if (path[1] == '\0') return (resolved); resolved_len = 1; left_len = strlcpy(left, path + 1, sizeof(left)); } else { if (getcwd(resolved, PATH_MAX) == NULL) { if (m) free(resolved); else { resolved[0] = '.'; resolved[1] = '\0'; } return (NULL); } resolved_len = strlen(resolved); left_len = strlcpy(left, path, sizeof(left)); } if (left_len >= sizeof(left) || resolved_len >= PATH_MAX) { if (m) free(resolved); errno = ENAMETOOLONG; return (NULL); } /* * Iterate over path components in `left'. */ while (left_len != 0) { /* * Extract the next path component and adjust `left' * and its length. */ p = strchr(left, '/'); s = p ? p : left + left_len; if (s - left >= sizeof(next_token)) { if (m) free(resolved); errno = ENAMETOOLONG; return (NULL); } memcpy(next_token, left, s - left); next_token[s - left] = '\0'; left_len -= s - left; if (p != NULL) memmove(left, s + 1, left_len + 1); if (resolved[resolved_len - 1] != '/') { if (resolved_len + 1 >= PATH_MAX) { if (m) free(resolved); errno = ENAMETOOLONG; return (NULL); } resolved[resolved_len++] = '/'; resolved[resolved_len] = '\0'; } if (next_token[0] == '\0') { /* * Handle consequential slashes. The path * before slash shall point to a directory. * * Only the trailing slashes are not covered * by other checks in the loop, but we verify * the prefix for any (rare) "//" or "/\0" * occurrence to not implement lookahead. */ if (lstat(resolved, &sb) != 0) { if (m) free(resolved); return (NULL); } if (!S_ISDIR(sb.st_mode)) { if (m) free(resolved); errno = ENOTDIR; return (NULL); } continue; } else if (strcmp(next_token, ".") == 0) continue; else if (strcmp(next_token, "..") == 0) { /* * Strip the last path component except when we have * single "/" */ if (resolved_len > 1) { resolved[resolved_len - 1] = '\0'; q = strrchr(resolved, '/') + 1; *q = '\0'; resolved_len = q - resolved; } continue; } /* * Append the next path component and lstat() it. */ resolved_len = strlcat(resolved, next_token, PATH_MAX); if (resolved_len >= PATH_MAX) { if (m) free(resolved); errno = ENAMETOOLONG; return (NULL); } if (lstat(resolved, &sb) != 0) { if (m) free(resolved); return (NULL); } if (S_ISLNK(sb.st_mode)) { if (symlinks++ > MAXSYMLINKS) { if (m) free(resolved); errno = ELOOP; return (NULL); } slen = readlink(resolved, symlink, sizeof(symlink) - 1); if (slen < 0) { if (m) free(resolved); return (NULL); } symlink[slen] = '\0'; if (symlink[0] == '/') { resolved[1] = 0; resolved_len = 1; } else if (resolved_len > 1) { /* Strip the last path component. */ resolved[resolved_len - 1] = '\0'; q = strrchr(resolved, '/') + 1; *q = '\0'; resolved_len = q - resolved; } /* * If there are any path components left, then * append them to symlink. The result is placed * in `left'. */ if (p != NULL) { if (symlink[slen - 1] != '/') { if (slen + 1 >= sizeof(symlink)) { if (m) free(resolved); errno = ENAMETOOLONG; return (NULL); } symlink[slen] = '/'; symlink[slen + 1] = 0; } left_len = strlcat(symlink, left, sizeof(symlink)); if (left_len >= sizeof(left)) { if (m) free(resolved); errno = ENAMETOOLONG; return (NULL); } } left_len = strlcpy(left, symlink, sizeof(left)); } } /* * Remove trailing slash except when the resolved pathname * is a single "/". */ if (resolved_len > 1 && resolved[resolved_len - 1] == '/') resolved[resolved_len - 1] = '\0'; return (resolved); } ================================================ FILE: src/task.lua ================================================ ---------------------------------------------------------------------------- -- Go style Channels for Lua -- https://github.com/majek/lua-channels -- -- This code is derived from libtask library by Russ Cox, mainly from -- channel.c. Semantically channels as implemented here are quite -- similar to channels from the Go language. -- -- Usage (we're using unbuffered channel here): -- -- local task = require('task') -- -- local function counter(channel) -- local i = 1 -- while true do -- channel:send(i) -- i = i + 1 -- end -- end -- -- local function main() -- local channel = task.Channel:new() -- task.spawn(counter, channel) -- assert(channel:recv() == 1) -- assert(channel:recv() == 2) -- assert(channel:recv() == 3) -- end -- -- task.spawn(main) -- task.scheduler() -- -- -- This module exposes: -- -- task.spawn(fun, [...]) - run fun as a coroutine with given -- parameters. You should use this instead of -- coroutine.create() -- -- task.scheduler() - can be run only from the main thread, executes -- all the stuff, resumes the coroutines that are -- blocked on channels that became available. You -- can only do non-blocking sends / receives from -- the main thread. -- -- task.Channel:new([buffer size]) - create a new channel with given size -- -- task.chanalt(alts, can_block) - run alt / select / multiplex over -- the alts structure. For example: -- -- task.chanalt({{c = channel_1, op = task.RECV}, -- {c = channel_2, op = task.SEND, p = "hello"}}, true) -- -- This will block current coroutine until it's possible to receive -- from channel_1 or send to channel_2. chanalt returns a number of -- statement from alts that succeeded (1 or 2 here) and a received -- value if executed statement was RECV. -- -- Finally, if two alt statements can be fulfilled at the same time, -- we use math.random() to decide which one should go first. So it -- makes sense to initialize seed with something random. If you don't -- have access to an entropy source you can do: -- math.randomseed(os.time()) -- but beware, the results of random() will predictable to a attacker. ---------------------------------------------------------------------------- local _M = {} -- Constants local RECV = 0x1 local SEND = 0x2 local NOP = 0x3 -- Global objects for scheduler local tasks_runnable = {} -- list of coroutines ready to be resumed ---------------------------------------------------------------------------- --- Helpers local function random_choice(arr) if #arr > 1 then return arr[math.random(#arr)] else return arr[1] end end -- Specialised Set data structure (with random element selection) local Set = { new = function(self) local o = {a = {}, l = {}}; setmetatable(o, self); self.__index = self return o end, add = function(self, v) local a, l = self.a, self.l if a[v] == nil then table.insert(l, v) a[v] = #l return true end end, remove = function(self, v) local a, l = self.a, self.l local i = a[v] if i > 0 then local t = l[#l] a[t], l[i] = i, t a[i], l[#l] = nil, nil return true end end, random = function(self, v) return random_choice(self.l) end, len = function(self) return #self.l end, } -- Circular Buffer data structure local CircularBuffer = { new = function(self, size) local o = {b = {}, slots = size + 1, size = size, l = 0, r = 0} setmetatable(o, self); self.__index = self return o end, len = function(self) return (self.r - self.l) % self.slots end, pop = function(self) assert(self.l ~= self.r) local v = self.b[self.l] self.l = (self.l + 1) % self.slots return v end, push = function(self, v) self.b[self.r] = v self.r = (self.r + 1) % self.slots assert(self.l ~= self.r) end, } ---------------------------------------------------------------------------- -- Scheduling -- -- Tasks ready to be run are placed on a stack and it's possible to -- starve a coroutine. local function scheduler() local self_coro, is_main = coroutine.running() -- We actually don't care if scheduler is run from the main -- coroutine. But we do need to make sure that user doesn't do -- blocking operation from it, as it can't yield. -- Be compatible with 5.1 and 5.2 assert(not(self_coro ~= nil and is_main ~= true), "Scheduler must be run from the main coroutine.") local i = 0 while #tasks_runnable > 0 do local co = table.remove(tasks_runnable) local okay, emsg = coroutine.resume(co) if not okay then error(emsg) end i = i + 1 end return i end local function task_ready(co) table.insert(tasks_runnable, co) end local function spawn(fun, ...) local args = {...} local f = function() fun(unpack(args)) end local co = coroutine.create(f) task_ready(co) end ---------------------------------------------------------------------------- -- Channels - chanalt and helpers -- Given two Alts from a single channel exchange data between -- them. It's implied that one is RECV and another is SEND. Channel -- may be buffered. local function altcopy(a, b) local r, s, c = a, b, a.c if r.op == SEND then r, s = s, r end assert(s == nil or s.op == SEND) assert(r == nil or r.op == RECV) -- Channel is empty or unbuffered, copy directly if s ~= nil and r and c._buf:len() == 0 then r.alt_array.value = s.p return end -- Otherwise it's always okay to receive and then send. if r ~= nil then r.alt_array.value = c._buf:pop() end if s ~= nil then c._buf:push(s.p) end end -- Given enqueued alt_array from a chanalt statement remove all alts -- from the associated channels. local function altalldequeue(alt_array) for i = 1, #alt_array do local a = alt_array[i] if a.op == RECV or a.op == SEND then a.c:_get_alts(a.op):remove(a) end end end -- Can this Alt be execed without blocking? local function altcanexec(a) local c, op = a.c, a.op if c._buf.size == 0 then if op ~= NOP then return c:_get_other_alts(op):len() > 0 end else if op == SEND then return c._buf:len() < c._buf.size elseif op == RECV then return c._buf:len() > 0 end end end -- Alt can be execed so find a counterpart Alt and exec it! local function altexec(a) local c, op = a.c, a.op local other_alts = c:_get_other_alts(op) local other_a = other_alts:random() -- other_a may be nil altcopy(a, other_a) if other_a ~= nil then -- Disengage from channels used by the other Alt and make it ready. altalldequeue(other_a.alt_array) other_a.alt_array.resolved = other_a.alt_index task_ready(other_a.alt_array.task) end end function ischannel(x) return type(x) == "table" and x.__index == _M.Channel end -- The main entry point. Call it `alt` or `select` or just a -- multiplexing statement. This is user facing function so make sure -- the parameters passed are sane. local function chanalt(alt_array, canblock) assert(#alt_array) local list_of_canexec_i = {} for i = 1, #alt_array do local a = alt_array[i] a.alt_array = alt_array a.alt_index = i assert(type(a.op) == "number" and (a.op == RECV or a.op == SEND or a.op == NOP), "op field must be RECV, SEND or NOP in alt") assert(ischannel(a.c), "pass valid channel to a c field of alt") if altcanexec(a) == true then table.insert(list_of_canexec_i, i) end end if #list_of_canexec_i > 0 then local i = random_choice(list_of_canexec_i) altexec(alt_array[i]) return i, alt_array.value end if canblock ~= true then return nil end local self_coro, is_main = coroutine.running() alt_array.task = self_coro assert(self_coro ~= nil and is_main ~= true, "Unable to block from the main thread, run scheduler.") for i = 1, #alt_array do local a = alt_array[i] if a.op ~= NOP then a.c:_get_alts(a.op):add(a) end end -- Make sure we're not woken by someone who is not the scheduler. alt_array.resolved = nil coroutine.yield() assert(alt_array.resolved > 0) local r = alt_array.resolved return r, alt_array.value end ---------------------------------------------------------------------------- -- Channel object local Channel = { new = function(self, buf_size) local o = {}; setmetatable(o, self); self.__index = self o._buf = CircularBuffer:new(buf_size or 0) o._recv_alts, o._send_alts = Set:new(), Set:new() return o end, send = function(self, msg) assert(chanalt({{c = self, op = SEND, p = msg}}, true) == 1) return true end, recv = function(self) local alts = {{c = self, op = RECV}} local s, msg = chanalt(alts, true) assert(s == 1) return msg end, nbsend = function(self, msg) local s = chanalt({{c = self, op = SEND, p = msg}}, false) return s == 1 end, nbrecv = function(self) local s, msg = chanalt({{c = self, op = RECV}}, false) return s == 1, msg end, _get_alts = function(self, op) return (op == RECV) and self._recv_alts or self._send_alts end, _get_other_alts = function(self, op) return (op == SEND) and self._recv_alts or self._send_alts end, __tostring = function(self) return string.format("", self._buf:len(), self._buf.size, self._send_alts:len(), self._recv_alts:len()) end, __call = function(self) local function f(s, v) return true, self:recv() end return f, nil, nil end, } ---------------------------------------------------------------------------- -- Public interface _M.scheduler = scheduler _M.spawn = spawn _M.Channel = Channel _M.chanalt = chanalt _M.RECV = RECV _M.SEND = SEND _M.NOP = NOP ---------------------------------------------------------------------------- ---------------------------------------------------------------------------- -- Tests local task = _M function test_counter() local done local function counter(c) local i = 1 while true do c:send(i) i = i + 1 end end local function main() local c = task.Channel:new() task.spawn(counter, c) assert(c:recv() == 1) assert(c:recv() == 2) assert(c:recv() == 3) assert(c:recv() == 4) assert(c:recv() == 5) done = true end task.spawn(main) task.scheduler() assert(done) end function test_nonblocking_channel() local done local function main() local b = task.Channel:new() assert(b:nbsend(1) == false) assert(b:nbrecv() == false) local c = task.Channel:new(1) assert(c:nbrecv() == false) assert(c:nbsend(1) == true) assert(c:nbsend(1) == false) local r, v = c:nbrecv() assert(r == true) assert(v == 1) assert(c:nbrecv() == false) done = true end task.spawn(main) task.scheduler() assert(done) end function test_concurrent_send_and_recv() local l = {} local function a(c, name) -- Blocking send and recv from the same process local alt = {{c = c, op = task.SEND, p = 1}, {c = c, op = task.RECV}} local i, v = task.chanalt(alt, true) local k = string.format('%s %s', name, i == 1 and "send" or "recv") l[k] = (l[k] or 0) + 1 end for i = 0, 1000 do -- On Mac OS X in lua 5.1 initializing seed with a -- predictable value makes no sense. For all seeds from 1 to -- 1000 the result of math.random(1,3) is _exactly_ the same! -- So beware, when seeding! -- math.randomseed(i) local c = task.Channel:new() task.spawn(a, c, "a") task.spawn(a, c, "b") task.scheduler() end -- Make sure we have randomness, that is: events occur in both -- orders in 1000 runs assert(l['a recv'] > 0) assert(l['a send'] > 0) assert(l['b recv'] > 0) assert(l['b send'] > 0) end function test_channels_from_a_coroutine() local done local c = task.Channel:new() local function a() for i = 1, 100 do c:send(i) end end local function b() assert(c:recv() == 1) assert(c:recv() == 2) assert(c:recv() == 3) assert(c:recv() == 4) assert(c:recv() == 5) done = true end local a_co = coroutine.create(a) local b_co = coroutine.create(b) coroutine.resume(a_co) coroutine.resume(b_co) task.scheduler() assert(done) end function test_fibonacci() local done local function fib(c) local x, y = 0, 1 while true do c:send(x) x, y = y, x + y end end local function main(c) assert(c:recv() == 0) assert(c:recv() == 1) assert(c:recv() == 1) assert(c:recv() == 2) assert(c:recv() == 3) assert(c:recv() == 5) assert(c:recv() == 8) assert(c:recv() == 13) assert(c:recv() == 21) assert(c:recv() == 34) done = true end local c = task.Channel:new() task.spawn(fib, c) task.spawn(main, c) task.scheduler() assert(done) end function test_non_blocking_chanalt() local done local function main() local c = task.Channel:new() local alts = {{c = c, op = task.RECV}, {c = c, op = task.NOP}, {c = c, op = task.SEND, p = 1}} assert(task.chanalt(alts, false) == nil) local c = task.Channel:new(1) local alts = {{c = c, op = task.RECV}, {c = c, op = task.NOP}, {c = c, op = task.SEND, p = 1}} assert(task.chanalt(alts, false) == 3) assert(task.chanalt(alts, false) == 1) local alts = {{c = c, op = task.NOP}} assert(task.chanalt(alts, false) == nil) done = true end task.spawn(main) task.scheduler() assert(done) end -- Apparently it's not really a Sieve of Eratosthenes: -- http://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf function test_eratosthenes_sieve() local done local function counter(c) local i = 2 while true do c:send(i) i = i + 1 end end local function filter(p, recv_ch, send_ch) while true do local i = recv_ch:recv() if i % p ~= 0 then send_ch:send(i) end end end local function sieve(primes_ch) local c = task.Channel:new() task.spawn(counter, c) while true do local p, newc = c:recv(), task.Channel:new() primes_ch:send(p) task.spawn(filter, p, c, newc) c = newc end end local function main() local primes = task.Channel:new() task.spawn(sieve, primes) assert(primes:recv() == 2) assert(primes:recv() == 3) assert(primes:recv() == 5) assert(primes:recv() == 7) assert(primes:recv() == 11) assert(primes:recv() == 13) done = true end task.spawn(main) task.scheduler() assert(done) end function test_channel_as_iterator() local done local function counter(c) local i = 2 while true do c:send(i) i = i + 1 end end local function main() local numbers = task.Channel:new() task.spawn(counter, numbers) for _, j in numbers() do if j == 100 then break end done = true end end if _VERSION == "Lua 5.1" then -- sorry, this test doesn't work in 5.1 print('S') done = true else task.spawn(main) task.scheduler() end assert(done) end return _M ================================================ FILE: src/teliva.c ================================================ #include #include #ifdef __NetBSD__ #include #else #include #endif #include #include #include #include #include "lua.h" #include "lauxlib.h" #include "lualib.h" #undef getstr #include "lstate.h" #include "teliva.h" #include "tlv.h" int starts_with(const char* s, const char* prefix) { return strncmp(s, prefix, strlen(prefix)) == 0; } int contains(const char* s, const char* sub) { return strstr(s, sub) != NULL; } int any_equal(char* const* arr, const char* s) { for (int i = 0; arr[i]; ++i) if (strcmp(arr[i], s) == 0) return 1; return 0; } int any_starts_with(char* const* arr, const char* s) { for (int i = 0; arr[i]; ++i) if (starts_with(s, arr[i])) return 1; return 0; } /*** Standard UI elements */ int menu_column = 0; void draw_string_on_menu(const char* s) { mvaddstr(LINES-1, menu_column, " "); ++menu_column; mvaddstr(LINES-1, menu_column, s); menu_column += strlen(s); mvaddstr(LINES-1, menu_column, " "); ++menu_column; } void draw_menu_item(const char* key, const char* name) { attroff(A_REVERSE); draw_string_on_menu(key); attron(A_REVERSE); draw_string_on_menu(name); } static const char* trim(const char* in) { static char result[1024]; int len = strlen(in); assert(len < 1020); const char* str = in; const char* end = in+len-1; while (isspace((unsigned char)*str)) { ++str; --len; } while (isspace((unsigned char)*end)) { --end; --len; } memset(result, '\0', 1024); memcpy(result, str, len); return result; } int ask_for_permission_on_every_file_operation = 0; const char* default_file_operations_predicate_body = "return false\n"; const char* file_operations_predicate_body; int net_operations_permitted = false; static void render_permissions(lua_State* L); char* Previous_message; static void draw_menu(lua_State* L) { attron(A_BOLD|A_REVERSE); color_set(COLOR_PAIR_MENU, NULL); for (int x = 0; x < COLS; ++x) mvaddch(LINES-1, x, ' '); menu_column = 2; draw_menu_item("^x", "exit"); /* if app ran successfully, render any app-specific items */ if (Previous_message == NULL) { lua_getglobal(L, "menu"); int table = lua_gettop(L); if (lua_istable(L, -1)) { for (int i = 1; i <= luaL_getn(L, table); ++i) { lua_rawgeti(L, table, i); int menu_item = lua_gettop(L); lua_rawgeti(L, menu_item, 1); /* key */ lua_rawgeti(L, menu_item, 2); /* value */ draw_menu_item(lua_tostring(L, -2), lua_tostring(L, -1)); lua_pop(L, 3); } } lua_pop(L, 1); } else { /* otherwise render the flash message */ attron(COLOR_PAIR(COLOR_PAIR_ERROR)); addstr(" "); addstr(Previous_message); addstr(" "); attroff(COLOR_PAIR(COLOR_PAIR_ERROR)); } /* render stuff common to all apps on the right */ menu_column = COLS-37; draw_menu_item("^u", "edit app"); draw_menu_item("^p", "perms"); attrset(A_NORMAL); mvaddstr(LINES-1, COLS-12, ""); render_permissions(L); attrset(A_NORMAL); } const char* character_name(char c) { if (c == '\n') return "ENTER"; if (c == '\t') return "TAB"; if (c == ' ') return "SPACE"; return "UNKNOWN"; } static void render_permissions(lua_State* L) { int file_colors = COLOR_PAIR_SAFE; if (ask_for_permission_on_every_file_operation) file_colors = COLOR_PAIR_WARN; else if (file_operations_predicate_body && strcmp("return false", trim(file_operations_predicate_body)) != 0) file_colors = COLOR_PAIR_WARN; int net_colors = net_operations_permitted ? COLOR_PAIR_WARN : COLOR_PAIR_SAFE; if (file_colors == COLOR_PAIR_WARN && net_colors == COLOR_PAIR_WARN) { file_colors = net_colors = COLOR_PAIR_RISK; } attron(COLOR_PAIR(file_colors)); addstr("file "); attron(A_REVERSE); addstr(" "); attroff(COLOR_PAIR(file_colors)); attron(COLOR_PAIR(net_colors)); addstr(" "); attroff(A_REVERSE); addstr(" net"); attroff(COLOR_PAIR(net_colors)); } void render_trusted_teliva_data(lua_State* L) { init_pair(COLOR_PAIR_ERROR, COLOR_ERROR_FOREGROUND, COLOR_ERROR_BACKGROUND); init_pair(COLOR_PAIR_MENU, COLOR_FOREGROUND, COLOR_BACKGROUND); init_pair(COLOR_PAIR_SAFE, COLOR_SAFE_REVERSE, COLOR_FOREGROUND); init_pair(COLOR_PAIR_WARN, COLOR_WARN_REVERSE, COLOR_FOREGROUND); init_pair(COLOR_PAIR_RISK, COLOR_RISK_REVERSE, COLOR_FOREGROUND); int y, x; getyx(stdscr, y, x); draw_menu(L); mvaddstr(y, x, ""); } /*** Error reporting */ const char* Previous_error = NULL; /* return final y containing text */ static int render_wrapped_text(int y, int xmin, int xmax, const char* text) { int x = xmin; move(y, x); for (int j = 0; j < strlen(text); ++j) { char c = text[j]; if (c != '\n') { addch(text[j]); ++x; if (x >= xmax) { ++y; x = xmin; move(y, x); } } else { /* newline */ ++y; x = xmin; move(y, x); } } return y; } void render_previous_error(void) { if (!Previous_error) return; init_pair(COLOR_PAIR_ERROR, COLOR_ERROR_FOREGROUND, COLOR_ERROR_BACKGROUND); attron(COLOR_PAIR(COLOR_PAIR_ERROR)); render_wrapped_text(LINES-10, COLS/2, COLS, Previous_error); attroff(COLOR_PAIR(COLOR_PAIR_ERROR)); } int report_in_developer_mode(lua_State* L, int status) { if (status && !lua_isnil(L, -1)) { Previous_error = strdup(lua_tostring(L, -1)); /* memory leak */ if (Previous_error == NULL) Previous_error = "(error object is not a string)"; lua_pop(L, 1); for (int x = 0; x < COLS; ++x) { mvaddch(LINES-2, x, ' '); mvaddch(LINES-1, x, ' '); } render_previous_error(); mvaddstr(LINES-1, 0, "press any key to continue"); getch(); developer_mode(L); } return status; } /*** Developer mode, big picture view */ #define CURRENT_DEFINITION_LEN 256 static void big_picture_menu(void) { attrset(A_REVERSE); for (int x = 0; x < COLS; ++x) mvaddch(LINES-1, x, ' '); attrset(A_NORMAL); menu_column = 2; draw_menu_item("^x", "go back"); draw_menu_item("^g", "go to highlight"); draw_menu_item("Enter", "submit"); draw_menu_item("^h", "backspace"); draw_menu_item("^u", "clear"); draw_menu_item("^r", "recent changes"); draw_menu_item("^e", "recent events"); attrset(A_NORMAL); } static int is_current_definition(lua_State* L, const char* definition_name, int current_history_array_index, int history_array_location, int history_array_size) { /* Sequentially scan back through history_array until current_history_array_index. * Is there an earlier definition of definition_name? */ int oldtop = lua_gettop(L); int found = 0; for (int i = history_array_size; i > current_history_array_index; --i) { lua_rawgeti(L, history_array_location, i); int t = lua_gettop(L); for (lua_pushnil(L); lua_next(L, t) != 0;) { lua_pop(L, 1); // value const char* curr = lua_tostring(L, -1); if (strcmp(curr, definition_name) == 0) { found = 1; lua_pop(L, 1); // key break; } // leave key on stack for next iteration } lua_pop(L, 1); // history element if (found) break; } if(oldtop != lua_gettop(L)) { endwin(); printf("%d %d\n", oldtop, lua_gettop(L)); exit(1); } return !found; } void draw_definition_name(const char* definition_name) { attron(COLOR_PAIR(COLOR_PAIR_SELECTABLE)); addstr(" "); addstr(definition_name); addstr(" "); attroff(COLOR_PAIR(COLOR_PAIR_SELECTABLE)); addstr(" "); } void draw_highlighted_definition_name(const char* definition_name) { attron(A_REVERSE); addstr(" "); addstr(definition_name); addstr(" "); attroff(A_REVERSE); addstr(" "); } void save_call_graph_depth(lua_State* L, int depth, const char* name) { /* Maintain a global table mapping from function name to call-stack depth * at first call to it. * * Won't be perfect; might get confused by shadowing locals. But we can't * be perfect without a bidirectional mapping between interpreter state * and source code. Which would make Lua either a lot less dynamic or a * a lot more like Smalltalk. */ // push table luaL_newmetatable(L, "__teliva_call_graph_depth"); int cgt = lua_gettop(L); // if key doesn't already exist, set it lua_getfield(L, cgt, name); if (lua_isnil(L, -1)) { lua_pushinteger(L, depth); lua_setfield(L, cgt, name); } // clean up lua_pop(L, 1); // value lua_pop(L, 1); // table } /* Don't rely on this for security. It's visible to apps and so can be mutated * by them. */ static const char* name_of_global(lua_State* L, const CallInfo* ci, int frame) { const char* result = NULL; Closure* func = ci_func(ci-frame); int oldtop = lua_gettop(L); // push table of function names luaL_newmetatable(L, "__teliva_global_name"); int gt = lua_gettop(L); lua_pushinteger(L, (long int)func); lua_rawget(L, gt); if (!lua_isnil(L, -1)) result = lua_tostring(L, -1); // safe because global names are long-lived and never GC'd lua_pop(L, 1); // value lua_pop(L, 1); // table of global names assert(lua_gettop(L) == oldtop); return result; } static void precompute_names_of_globals(lua_State* L) { int oldtop = lua_gettop(L); luaL_newmetatable(L, "__teliva_global_name"); int gt = lua_gettop(L); lua_pushvalue(L, LUA_GLOBALSINDEX); int table = lua_gettop(L); for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) { const char* key = lua_tostring(L, -2); const void* value = lua_topointer(L, -1); lua_pushinteger(L, (long int)value); lua_pushstring(L, key); lua_rawset(L, gt); } lua_pop(L, 1); // table of globals lua_pop(L, 1); // table of global names assert(lua_gettop(L) == oldtop); } static void save_caller(lua_State* L, const char* name, const char* caller_name) { int oldtop = lua_gettop(L); // push table of caller tables luaL_newmetatable(L, "__teliva_caller"); int ct = lua_gettop(L); // if key doesn't already exist, map it to an empty caller table lua_getfield(L, ct, name); if (lua_isnil(L, -1)) { lua_newtable(L); lua_setfield(L, ct, name); } // append the caller's name to the caller table if necessary lua_pop(L, 1); // old value lua_getfield(L, ct, name); // new value = caller table int curr_caller_index = lua_gettop(L); lua_pushboolean(L, true); lua_setfield(L, curr_caller_index, caller_name); // clean up lua_pop(L, 1); // caller table lua_pop(L, 1); // table of caller tables assert(lua_gettop(L) == oldtop); } void record_metadata_about_function_call (lua_State *L, CallInfo *ci) { const char* function_name = name_of_global(L, ci, 0); long int call_graph_depth = ci - L->base_ci; /* note to self: the function pointer is at ci_func(ci) */ if (function_name) { save_call_graph_depth(L, call_graph_depth, function_name); if (call_graph_depth <= 1) return; const char* caller_name = name_of_global(L, ci, 1); if (caller_name) save_caller(L, function_name, caller_name); else if (call_graph_depth == 3) save_caller(L, function_name, "main"); } } static void clear_caller(lua_State* L) { int oldtop = lua_gettop(L); luaL_newmetatable(L, "__teliva_caller"); int ct = lua_gettop(L); lua_pushnil(L); while (lua_next(L, ct) != 0) { lua_pop(L, 1); /* old value */ lua_pushvalue(L, -1); /* duplicate key */ lua_pushnil(L); /* new value */ lua_settable(L, ct); /* one copy of key left for lua_next */ } lua_pop(L, 1); assert(lua_gettop(L) == oldtop); } /* return true if submitted */ static int edit_current_definition(lua_State* L); static void recent_changes_view(lua_State* L); static const char* events_view(); static int look_up_definition (lua_State* L, const char* name); void default_big_picture_view(lua_State* L) { /* Without any intervening edits, big_picture_view always stably renders * definitions in exactly the same spatial order, both in levels from top to * bottom and in indexes within each level from left to right. */ int highlight_level = 0; int highlight_index_within_level = 0; int level_size[30] = {0}; /* number of indexes within each level */ char highlight[CURRENT_DEFINITION_LEN+1] = {0}; restart: clear(); luaL_newmetatable(L, "__teliva_call_graph_depth"); int cgt = lua_gettop(L); // segment definitions by depth lua_getglobal(L, "teliva_program"); int history_array = lua_gettop(L); int history_array_size = luaL_getn(L, history_array); int y = 1; attrset(A_BOLD); mvaddstr(y, 0, "Big picture"); attrset(A_NORMAL); y += 2; int found = look_up_definition(L, "doc:blurb"); if (found) { assert(lua_isstring(L, -1)); y = render_wrapped_text(y, 8, 68, lua_tostring(L, -1)); y += 2; lua_pop(L, 1); } mvaddstr(y, 0, "data: "); // first: data (non-functions) that's not the Teliva menu or curses variables if (highlight_level < 0) highlight_level = 0; int level = 0; int index_within_level = 0; for (int i = history_array_size; i > 0; --i) { lua_rawgeti(L, history_array, i); int t = lua_gettop(L); for (lua_pushnil(L); lua_next(L, t) != 0; lua_pop(L, 1)) { const char* definition_name = lua_tostring(L, -2); if (is_special_history_key(definition_name)) continue; if (starts_with(definition_name, "doc:")) continue; lua_getglobal(L, definition_name); int is_userdata = lua_isuserdata(L, -1); int is_function = lua_isfunction(L, -1); lua_pop(L, 1); if (strcmp(definition_name, "menu") != 0 // required by all Teliva programs && !is_function // functions are not data && !is_userdata // including curses window objects // (unlikely to have an interesting definition) ) { if (is_current_definition(L, definition_name, i, history_array, history_array_size)) { if (level == highlight_level && index_within_level == highlight_index_within_level) { draw_highlighted_definition_name(definition_name); strncpy(highlight, definition_name, CURRENT_DEFINITION_LEN); } else { draw_definition_name(definition_name); } ++index_within_level; } } } lua_pop(L, 1); // history element } // second: menu and other userdata for (int i = history_array_size; i > 0; --i) { lua_rawgeti(L, history_array, i); int t = lua_gettop(L); for (lua_pushnil(L); lua_next(L, t) != 0; lua_pop(L, 1)) { const char* definition_name = lua_tostring(L, -2); if (is_special_history_key(definition_name)) continue; if (starts_with(definition_name, "doc:")) continue; lua_getglobal(L, definition_name); int is_userdata = lua_isuserdata(L, -1); lua_pop(L, 1); if (strcmp(definition_name, "menu") == 0 || is_userdata // including curses window objects ) { if (is_current_definition(L, definition_name, i, history_array, history_array_size)) { if (level == highlight_level && index_within_level == highlight_index_within_level) { draw_highlighted_definition_name(definition_name); strncpy(highlight, definition_name, CURRENT_DEFINITION_LEN); } else { draw_definition_name(definition_name); } ++index_within_level; } } } lua_pop(L, 1); // history element } level_size[level] = index_within_level; level++; // documentation (non-code) buffers y += 2; mvprintw(y, 0, "prose: "); index_within_level = 0; for (int i = history_array_size; i > 0; --i) { lua_rawgeti(L, history_array, i); int t = lua_gettop(L); for (lua_pushnil(L); lua_next(L, t) != 0; lua_pop(L, 1)) { const char* definition_name = lua_tostring(L, -2); if (is_special_history_key(definition_name)) continue; if (starts_with(definition_name, "doc:")) { if (is_current_definition(L, definition_name, i, history_array, history_array_size)) { if (level == highlight_level && index_within_level == highlight_index_within_level) { draw_highlighted_definition_name(definition_name); strncpy(highlight, definition_name, CURRENT_DEFINITION_LEN); } else { draw_definition_name(definition_name); } ++index_within_level; } } } lua_pop(L, 1); // history element } level_size[level] = index_within_level; level++; // functions by level y += 2; mvprintw(y, 0, "functions: "); y++; for (int depth = /*main*/2; ; ++depth) { mvaddstr(y, 0, " "); bool drew_anything = false; index_within_level = 0; for (int i = history_array_size; i > 0; --i) { lua_rawgeti(L, history_array, i); int t = lua_gettop(L); for (lua_pushnil(L); lua_next(L, t) != 0; lua_pop(L, 1)) { const char* definition_name = lua_tostring(L, -2); if (is_special_history_key(definition_name)) continue; lua_getfield(L, cgt, definition_name); int definition_depth = lua_tointeger(L, -1); if (definition_depth == depth) { if (is_current_definition(L, definition_name, i, history_array, history_array_size)) { if (level == highlight_level && index_within_level == highlight_index_within_level) { draw_highlighted_definition_name(definition_name); strncpy(highlight, definition_name, CURRENT_DEFINITION_LEN); } else { draw_definition_name(definition_name); } ++index_within_level; } drew_anything = true; } lua_pop(L, 1); // depth of value } lua_pop(L, 1); // history element } y += 2; if (!drew_anything) break; level_size[level] = index_within_level; level++; } // unused functions mvaddstr(y, 0, " "); /* no need to level++ because the final iteration above didn't draw anything */ index_within_level = 0; for (int i = history_array_size; i > 0; --i) { lua_rawgeti(L, history_array, i); int t = lua_gettop(L); for (lua_pushnil(L); lua_next(L, t) != 0; lua_pop(L, 1)) { const char* definition_name = lua_tostring(L, -2); if (is_special_history_key(definition_name)) continue; lua_getglobal(L, definition_name); int is_function = lua_isfunction(L, -1); lua_pop(L, 1); lua_getfield(L, cgt, definition_name); if (is_function && lua_isnoneornil(L, -1)) { if (is_current_definition(L, definition_name, i, history_array, history_array_size)) { if (level == highlight_level && index_within_level == highlight_index_within_level) { draw_highlighted_definition_name(definition_name); strncpy(highlight, definition_name, CURRENT_DEFINITION_LEN); } else { draw_definition_name(definition_name); } ++index_within_level; } } lua_pop(L, 1); // depth of value } lua_pop(L, 1); // history element } level_size[level] = index_within_level; int max_level = level; lua_settop(L, 0); render_previous_error(); char query[CURRENT_DEFINITION_LEN+1] = {0}; int qlen = 0; while (1) { big_picture_menu(); for (int x = 0; x < COLS; ++x) mvaddch(LINES-2, x, ' '); //? mvprintw(20, 60, "%d %d\n", highlight_level, highlight_index_within_level); mvprintw(LINES-2, 0, "Edit: %s", query); int c = getch(); if (c == KEY_BACKSPACE || c == DELETE || c == CTRL_H) { if (qlen != 0) query[--qlen] = '\0'; } else if (c == CTRL_X) { return; } else if (c == ENTER) { if (query[0] != '\0') { save_to_current_definition_and_editor_buffer(L, query); int back_to_big_picture = edit_current_definition(L); if (back_to_big_picture) goto restart; return; } } else if (c == CTRL_U) { qlen = 0; query[qlen] = '\0'; } else if (c == CTRL_R) { recent_changes_view(L); goto restart; } else if (c == KEY_LEFT) { highlight_index_within_level--; if (highlight_index_within_level < 0) highlight_index_within_level = 0; goto restart; } else if (c == KEY_RIGHT) { highlight_index_within_level++; if (highlight_index_within_level >= level_size[highlight_level]) highlight_index_within_level = level_size[highlight_level]-1; if (highlight_index_within_level < 0) highlight_index_within_level = 0; goto restart; } else if (c == KEY_UP) { highlight_level--; if (highlight_level < 0) highlight_level = 0; if (highlight_index_within_level >= level_size[highlight_level]) highlight_index_within_level = level_size[highlight_level]-1; if (highlight_index_within_level < 0) highlight_index_within_level = 0; goto restart; } else if (c == KEY_DOWN) { highlight_level++; if (highlight_level > max_level) highlight_level = max_level; if (highlight_index_within_level >= level_size[highlight_level]) highlight_index_within_level = level_size[highlight_level]-1; if (highlight_index_within_level < 0) highlight_index_within_level = 0; goto restart; } else if (c == CTRL_G) { save_to_current_definition_and_editor_buffer(L, highlight); int back_to_big_picture = edit_current_definition(L); if (back_to_big_picture) goto restart; return; } else if (c == CTRL_E) { const char* definition = events_view(); if (definition) { save_to_current_definition_and_editor_buffer(L, definition); int back_to_big_picture = edit_current_definition(L); if (back_to_big_picture) goto restart; } return; } else if (isprint(c)) { if (qlen < CURRENT_DEFINITION_LEN) { query[qlen++] = c; query[qlen] = '\0'; } } } /* never gets here */ } extern int edit(lua_State* L, char* filename, char* definition_name); void big_picture_view(lua_State* L) { int oldtop = lua_gettop(L); if (!look_up_definition(L, "doc:main")) { lua_settop(L, oldtop); default_big_picture_view(L); } else { save_to_current_definition_and_editor_buffer(L, "doc:main"); int back_to_big_picture = edit_current_definition(L); if (back_to_big_picture) default_big_picture_view(L); } lua_settop(L, oldtop); } /* return true if: * - editor_state exists, and * - editor_state is applicable to the current image * Implicitly loads current editor state. */ int editor_view_in_progress(lua_State* L) { FILE* in = fopen("teliva_editor_state", "r"); if (in == NULL) return 0; int oldtop = lua_gettop(L); teliva_load_definition(L, in); int t = lua_gettop(L); lua_getfield(L, t, "image"); const char* image_name = lua_tostring(L, -1); int result = (strcmp(image_name, Image_name) == 0); lua_pop(L, 1); /* image value */ lua_setglobal(L, "__teliva_editor_state"); assert(lua_gettop(L) == oldtop); return result; } char Current_definition[CURRENT_DEFINITION_LEN+1] = {0}; void draw_current_definition_name_and_callers(lua_State* L) { int oldtop = lua_gettop(L); mvaddstr(0, 0, ""); draw_definition_name(Current_definition); luaL_newmetatable(L, "__teliva_caller"); int ct = lua_gettop(L); lua_getfield(L, ct, Current_definition); if (lua_isnil(L, -1)) { lua_pop(L, 2); assert(oldtop == lua_gettop(L)); return; } int ctc = lua_gettop(L); attron(COLOR_PAIR(COLOR_PAIR_FADE)); addstr("callers: "); attroff(COLOR_PAIR(COLOR_PAIR_FADE)); for (lua_pushnil(L); lua_next(L, ctc) != 0; lua_pop(L, 1)) { const char* caller_name = lua_tostring(L, -2); draw_definition_name(caller_name); } lua_pop(L, 2); // caller table, __teliva_caller assert(oldtop == lua_gettop(L)); } extern int resumeEdit(lua_State* L); extern int editFrom(lua_State* L, char* filename, char* definition_name, int rowoff, int coloff, int cy, int cx); int restore_editor_view(lua_State* L) { lua_getglobal(L, "__teliva_editor_state"); int editor_state_index = lua_gettop(L); lua_getfield(L, editor_state_index, "definition"); const char* definition = lua_tostring(L, -1); save_to_current_definition_and_editor_buffer(L, definition); lua_getfield(L, editor_state_index, "rowoff"); int rowoff = lua_tointeger(L, -1); lua_getfield(L, editor_state_index, "coloff"); int coloff = lua_tointeger(L, -1); lua_getfield(L, editor_state_index, "cy"); int cy = lua_tointeger(L, -1); lua_getfield(L, editor_state_index, "cx"); int cx = lua_tointeger(L, -1); lua_settop(L, editor_state_index); int back_to_big_picture = editFrom(L, "teliva_editor_buffer", Current_definition, rowoff, coloff, cy, cx); if (starts_with(Current_definition, "doc:")) { load_editor_buffer_to_current_definition_in_image(L); return back_to_big_picture; } // error handling int oldtop = lua_gettop(L); while (1) { int status; status = load_editor_buffer_to_current_definition_in_image_and_reload(L); if (status == 0 || lua_isnil(L, -1)) break; Previous_error = lua_tostring(L, -1); if (Previous_error == NULL) Previous_error = "(error object is not a string)"; back_to_big_picture = resumeEdit(L); lua_pop(L, 1); } if (lua_gettop(L) != oldtop) { endwin(); printf("editFrom: memory leak %d -> %d\n", oldtop, lua_gettop(L)); exit(1); } return back_to_big_picture; } char** Argv = NULL; extern void cleanup_curses(void); void developer_mode(lua_State* L) { /* clobber the app's ncurses colors; we'll restart the app when we rerun it. */ assume_default_colors(COLOR_FOREGROUND, COLOR_BACKGROUND); init_pair(COLOR_PAIR_NORMAL, COLOR_FOREGROUND, COLOR_BACKGROUND); init_pair(COLOR_PAIR_SELECTABLE, COLOR_SELECTABLE_FOREGROUND, COLOR_SELECTABLE_BACKGROUND); init_pair(COLOR_PAIR_FADE, COLOR_FADE, COLOR_BACKGROUND); init_pair(COLOR_PAIR_MENU_ALTERNATE, COLOR_MENU_ALTERNATE, COLOR_BACKGROUND); init_pair(COLOR_PAIR_LUA_COMMENT, COLOR_LUA_COMMENT, COLOR_BACKGROUND); init_pair(COLOR_PAIR_LUA_KEYWORD, COLOR_LUA_KEYWORD, COLOR_BACKGROUND); init_pair(COLOR_PAIR_LUA_CONSTANT, COLOR_LUA_CONSTANT, COLOR_BACKGROUND); init_pair(COLOR_PAIR_MATCH, COLOR_MATCH_FOREGROUND, COLOR_MATCH_BACKGROUND); init_pair(COLOR_PAIR_ERROR, COLOR_ERROR_FOREGROUND, COLOR_ERROR_BACKGROUND); nodelay(stdscr, 0); /* always make getch() block in developer mode */ curs_set(1); /* always display cursor in developer mode */ int switch_to_big_picture_view = 1; if (editor_view_in_progress(L)) switch_to_big_picture_view = restore_editor_view(L); if (switch_to_big_picture_view) big_picture_view(L); cleanup_curses(); execv(Argv[0], Argv); /* never returns */ } void save_editor_state(int rowoff, int coloff, int cy, int cx) { if (strlen(Current_definition) == 0) return; char outfilename[] = "teliva_editor_state_XXXXXX"; int outfd = mkstemp(outfilename); if (outfd == -1) { endwin(); perror("error in creating temporary file"); abort(); } FILE* out = fdopen(outfd, "w"); assert(out != NULL); fprintf(out, "- image: %s\n", Image_name); fprintf(out, " definition: %s\n", Current_definition); fprintf(out, " rowoff: %d\n", rowoff); fprintf(out, " coloff: %d\n", coloff); fprintf(out, " cy: %d\n", cy); fprintf(out, " cx: %d\n", cx); fclose(out); rename(outfilename, "teliva_editor_state"); } /* when found, return 1 and leave string on top of stack * when not found, return 0 * caller is responsible for cleaning up the stack. */ static int look_up_definition (lua_State* L, const char* name) { lua_getglobal(L, "teliva_program"); int history_array = lua_gettop(L); /* iterate over mutations in teliva_program history in reverse order */ int history_array_size = luaL_getn(L, history_array); for (int i = history_array_size; i > 0; --i) { lua_rawgeti(L, history_array, i); int table = lua_gettop(L); /* iterate over bindings */ /* really we expect only one */ for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) { const char* key = lua_tostring(L, -2); if (strcmp(key, "__teliva_undo") == 0) { int next_i = lua_tointeger(L, -1); assert(next_i < i); i = next_i + 1; /* account for decrement */ lua_pop(L, 1); break; } if (is_special_history_key(key)) continue; if (strcmp(key, name) == 0) return 1; } lua_pop(L, 1); } lua_pop(L, 1); return 0; } void save_to_current_definition_and_editor_buffer(lua_State* L, const char* definition) { int oldtop = lua_gettop(L); strncpy(Current_definition, definition, CURRENT_DEFINITION_LEN); int status = look_up_definition(L, Current_definition); char outfilename[] = "teliva_editor_buffer_XXXXXX"; int outfd = mkstemp(outfilename); if (outfd == -1) { endwin(); perror("save_to_current_definition_and_editor_buffer: error in creating temporary file"); abort(); } FILE* out = fdopen(outfd, "w"); assert(out != NULL); if (status) fprintf(out, "%s", lua_tostring(L, -1)); fclose(out); rename(outfilename, "teliva_editor_buffer"); lua_settop(L, oldtop); } /* I don't understand the best way to read all of a text file. * I'm currently using fread, but its error handling is really designed for * binary data containing fixed-size records. */ static void read_editor_buffer(char* out, int capacity) { FILE* in = fopen("teliva_editor_buffer", "r"); fread(out, capacity, 1, in); /* TODO: handle overly large file */ fclose(in); } static void update_definition(lua_State* L, const char* name, char* new_contents) { int oldtop = lua_gettop(L); /* if contents are unmodified, return */ if (look_up_definition(L, name)) { const char* old_contents = lua_tostring(L, -1); bool contents_unmodified = (strcmp(old_contents, new_contents) == 0); lua_settop(L, oldtop); if (contents_unmodified) return; } lua_getglobal(L, "teliva_program"); int history_array = lua_gettop(L); /* create a new table containing a single binding */ lua_createtable(L, /*number of fields per mutation*/2, 0); lua_pushstring(L, new_contents); assert(strlen(name) > 0); lua_setfield(L, -2, name); /* include timestamp at which binding was created */ time_t t; time(&t); char* time_string = ctime(&t); lua_pushstring(L, time_string); lua_setfield(L, -2, "__teliva_timestamp"); /* append the new table to the history of mutations */ int history_array_size = luaL_getn(L, history_array); ++history_array_size; lua_rawseti(L, history_array, history_array_size); lua_settop(L, oldtop); } extern void save_tlv(lua_State* L, char* filename); void load_editor_buffer_to_current_definition_in_image(lua_State* L) { char new_contents[8192] = {0}; read_editor_buffer(new_contents, 8190); update_definition(L, Current_definition, new_contents); save_tlv(L, Image_name); } extern int docall(lua_State* L, int narg, int clear); int load_editor_buffer_to_current_definition_in_image_and_reload(lua_State* L) { char new_contents[8192] = {0}; read_editor_buffer(new_contents, 8190); update_definition(L, Current_definition, new_contents); save_tlv(L, Image_name); /* reload binding */ return luaL_loadbuffer(L, new_contents, strlen(new_contents), Current_definition) || docall(L, 0, 1); } /* return true if user chose to back into the big picture view */ /* But only if there are no errors. Otherwise things can get confusing. */ static int edit_current_definition(lua_State* L) { int back_to_big_picture = edit(L, "teliva_editor_buffer", Current_definition); if (starts_with(Current_definition, "doc:")) { load_editor_buffer_to_current_definition_in_image(L); return back_to_big_picture; } // error handling int oldtop = lua_gettop(L); while (1) { int status; status = load_editor_buffer_to_current_definition_in_image_and_reload(L); if (status == 0 || lua_isnil(L, -1)) break; Previous_error = lua_tostring(L, -1); if (Previous_error == NULL) Previous_error = "(error object is not a string)"; back_to_big_picture = resumeEdit(L); lua_pop(L, 1); } if (lua_gettop(L) != oldtop) { endwin(); printf("edit_current_definition: memory leak %d -> %d\n", oldtop, lua_gettop(L)); exit(1); } return back_to_big_picture; } static void recent_changes_menu(int cursor, int history_array_size) { attrset(A_REVERSE); for (int x = 0; x < COLS; ++x) mvaddch(LINES-1, x, ' '); attrset(A_NORMAL); menu_column = 2; draw_menu_item("^x", "go back"); /* draw_menu_item("↓|space", "older"); */ attroff(A_REVERSE); mvaddstr(LINES-1, menu_column, " ↓"); attron(COLOR_PAIR(COLOR_PAIR_MENU_ALTERNATE)); addstr("|"); attroff(COLOR_PAIR(COLOR_PAIR_MENU_ALTERNATE)); addstr("space "); menu_column += 9; /* strlen isn't sufficient */ attron(A_REVERSE); draw_string_on_menu("older"); /* draw_menu_item("↑|backspace|delete|^h", "newer"); */ attroff(A_REVERSE); mvaddstr(LINES-1, menu_column, " ↑"); attron(COLOR_PAIR(COLOR_PAIR_MENU_ALTERNATE)); addstr("|"); attroff(COLOR_PAIR(COLOR_PAIR_MENU_ALTERNATE)); addstr("backspace"); attron(COLOR_PAIR(COLOR_PAIR_MENU_ALTERNATE)); addstr("|"); attroff(COLOR_PAIR(COLOR_PAIR_MENU_ALTERNATE)); addstr("delete"); attron(COLOR_PAIR(COLOR_PAIR_MENU_ALTERNATE)); addstr("|"); attroff(COLOR_PAIR(COLOR_PAIR_MENU_ALTERNATE)); addstr("^h "); menu_column += 23; attron(A_REVERSE); draw_string_on_menu("newer"); draw_menu_item("^e", "edit note"); if (cursor < history_array_size) draw_menu_item("^u", "undo everything after this"); attrset(A_NORMAL); } /* return final y containing text */ static int render_wrapped_lua_text(int y, int xmin, int xmax, const char* text) { int x = xmin; move(y, x); for (int j = 0; j < strlen(text); ++j) { char c = text[j]; if (c == '-' && j+1 < strlen(text) && text[j+1] == '-') attron(COLOR_PAIR(COLOR_PAIR_LUA_COMMENT)); if (c != '\n') { addch(text[j]); ++x; if (x >= xmax) { ++y; x = xmin; move(y, x); } } else { /* newline */ ++y; x = xmin; move(y, x); attroff(COLOR_PAIR(COLOR_PAIR_LUA_COMMENT)); } } return y; } static void render_recent_changes(lua_State* L, int start_index) { clear(); attrset(A_BOLD); mvaddstr(1, 0, "Recent changes"); attrset(A_NORMAL); int oldtop = lua_gettop(L); lua_getglobal(L, "teliva_program"); int history_array = lua_gettop(L); int history_array_size = luaL_getn(L, history_array); int y = 3; attron(A_REVERSE); for (int i = start_index; i > 0; --i) { attron(A_BOLD); mvprintw(y, 0, "%3d. ", i); attrset(A_NORMAL); lua_rawgeti(L, history_array, i); int t = lua_gettop(L); for (lua_pushnil(L); lua_next(L, t) != 0; lua_pop(L, 1)) { if (strcmp(lua_tostring(L, -2), "__teliva_undo") == 0) { addstr("undo to "); attron(A_BOLD); printw("%d", lua_tointeger(L, -1)); attroff(A_BOLD); y++; continue; } const char* definition_name = lua_tostring(L, -2); if (is_special_history_key(definition_name)) continue; addstr(definition_name); /* save timestamp of binding if available */ lua_getfield(L, t, "__teliva_timestamp"); if (!lua_isnil(L, -1)) { char buffer[128] = {0}; strncpy(buffer, lua_tostring(L, -1), 120); if (buffer[strlen(buffer)-1] == '\n') buffer[strlen(buffer)-1] = '\0'; attron(COLOR_PAIR(COLOR_PAIR_FADE)); printw(" %s", buffer); attroff(COLOR_PAIR(COLOR_PAIR_FADE)); } lua_pop(L, 1); lua_getfield(L, t, "__teliva_note"); if (!lua_isnil(L, -1)) { attron(COLOR_PAIR(COLOR_PAIR_FADE)); printw(" -- %s", lua_tostring(L, -1)); attroff(COLOR_PAIR(COLOR_PAIR_FADE)); } lua_pop(L, 1); y++; const char* definition_contents = lua_tostring(L, -1); y = render_wrapped_lua_text(y, 0, COLS, definition_contents); y++; if (y >= LINES-1) break; /* leave cruft on the stack */ } lua_settop(L, t); /* clean up cruft on the stack */ lua_pop(L, 1); // history element y++; if (y >= LINES-1) break; } lua_pop(L, 1); // history array if (lua_gettop(L) != oldtop) { endwin(); printf("render_recent_changes: memory leak %d -> %d\n", oldtop, lua_gettop(L)); exit(1); } recent_changes_menu(start_index, history_array_size); refresh(); } static void add_undo_event(lua_State* L, int cursor) { lua_getglobal(L, "teliva_program"); int history_array = lua_gettop(L); /* create a new table containing the undo event */ lua_createtable(L, /*number of fields per mutation*/2, 0); lua_pushinteger(L, cursor); lua_setfield(L, -2, "__teliva_undo"); /* include timestamp at which event was created */ time_t t; time(&t); char* time_string = ctime(&t); lua_pushstring(L, time_string); lua_setfield(L, -2, "__teliva_timestamp"); /* append the new table to the history of mutations */ int history_array_size = luaL_getn(L, history_array); ++history_array_size; lua_rawseti(L, history_array, history_array_size); /* clean up */ lua_pop(L, 1); } static void save_note_to_editor_buffer(lua_State* L, int cursor) { lua_getglobal(L, "teliva_program"); lua_rawgeti(L, -1, cursor); lua_getfield(L, -1, "__teliva_note"); const char* contents = lua_tostring(L, -1); char outfilename[] = "teliva_editor_buffer_XXXXXX"; int outfd = mkstemp(outfilename); if (outfd == -1) { endwin(); perror("save_note_to_editor_buffer: error in creating temporary file"); abort(); } FILE* out = fdopen(outfd, "w"); assert(out != NULL); if (contents != NULL) fprintf(out, "%s", contents); fclose(out); rename(outfilename, "teliva_editor_buffer"); lua_pop(L, 3); /* contents, table at cursor, teliva_program */ } static void load_note_from_editor_buffer(lua_State* L, int cursor) { lua_getglobal(L, "teliva_program"); char new_contents[8192] = {0}; read_editor_buffer(new_contents, 8190); lua_rawgeti(L, -1, cursor); lua_pushstring(L, new_contents); lua_setfield(L, -2, "__teliva_note"); lua_pop(L, 2); /* table at cursor, teliva_program */ } extern void editNonCode(char* filename); static void recent_changes_view(lua_State* L) { lua_getglobal(L, "teliva_program"); int history_array = lua_gettop(L); assert(history_array == 1); int history_array_size = luaL_getn(L, history_array); int cursor = history_array_size; lua_pop(L, 1); int quit = 0; while (!quit) { /* refresh state after each operation so we pick up modifications */ render_recent_changes(L, cursor); int c = getch(); switch (c) { case CTRL_X: quit = 1; break; case KEY_DOWN: case ' ': if (cursor > 1) --cursor; break; case KEY_UP: case KEY_BACKSPACE: case DELETE: case CTRL_H: if (cursor < history_array_size) ++cursor; break; case CTRL_E: save_note_to_editor_buffer(L, cursor); /* big picture hotkey unnecessarily available here */ editNonCode("teliva_editor_buffer"); load_note_from_editor_buffer(L, cursor); save_tlv(L, Image_name); break; case CTRL_U: if (cursor < history_array_size) { add_undo_event(L, cursor); save_tlv(L, Image_name); } break; } } } static int binding_exists (lua_State *L, const char *name) { int result = 0; lua_getglobal(L, name); result = !lua_isnil(L, -1); lua_pop(L, 1); return result; } extern int dostring(lua_State* L, const char* s, const char* name); static int load_definitions(lua_State* L) { int status; lua_getglobal(L, "teliva_program"); int history_array = lua_gettop(L); /* iterate over mutations in teliva_program history in reverse order */ int history_array_size = luaL_getn(L, history_array); for (int i = history_array_size; i > 0; --i) { lua_rawgeti(L, history_array, i); int table = lua_gettop(L); /* iterate over bindings */ /* really we expect only one */ for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) { const char* key = lua_tostring(L, -2); if (strcmp(key, "__teliva_undo") == 0) { int next_i = lua_tointeger(L, -1); assert(next_i < i); i = next_i + 1; /* account for decrement */ lua_pop(L, 1); break; } if (is_special_history_key(key)) continue; if (starts_with(key, "doc:")) continue; if (binding_exists(L, key)) continue; // most recent binding trumps older ones const char* value = lua_tostring(L, -1); status = dostring(L, value, key); if (status != 0) return report_in_developer_mode(L, status); } lua_pop(L, 1); } lua_pop(L, 1); return 0; } static int run_tests(lua_State* L) { clear(); int oldtop = lua_gettop(L); lua_pushinteger(L, 0); lua_setglobal(L, "teliva_num_test_failures"); lua_pushnil(L); lua_setglobal(L, "teliva_first_failure"); lua_pushvalue(L, LUA_GLOBALSINDEX); int table = lua_gettop(L); for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) { const char* key = lua_tostring(L, -2); if (strncmp("test_", key, strlen("test_")) != 0) continue; if (!lua_isfunction(L, -1)) continue; /* uncomment these lines when it's not clear which test is failing */ //? addstr(key); //? addstr("|"); int status = lua_pcall(L, 0, 0, 0); if (status) { printw("E%d: %s", status, lua_tostring(L, -1)); /* increment teliva_num_test_failures */ lua_getglobal(L, "teliva_num_test_failures"); int num_failures = lua_tointeger(L, -1); lua_pop(L, 1); lua_pushinteger(L, num_failures+1); lua_setglobal(L, "teliva_num_test_failures"); /* if unset, set teliva_first_failure */ lua_getglobal(L, "teliva_first_failure"); int first_failure_clear = lua_isnil(L, -1); lua_pop(L, 1); if (first_failure_clear) lua_setglobal(L, "teliva_first_failure"); else lua_pop(L, 1); } lua_pushnil(L); /* just to undo loop update */ } lua_pop(L, 1); lua_getglobal(L, "teliva_num_test_failures"); int num_failures = lua_tointeger(L, -1); lua_pop(L, 1); if (lua_gettop(L) != oldtop) { endwin(); printf("render_recent_changes: memory leak %d -> %d\n", oldtop, lua_gettop(L)); exit(1); } if (num_failures == 0) return 0; if (num_failures == 1) addstr("1 failure"); else printw("%d failures", num_failures); /* take first failure back to developer mode */ lua_getglobal(L, "teliva_first_failure"); assert(!lua_isnil(L, -1)); return 1; } static void clear_call_graph_depth(lua_State* L) { int oldtop = lua_gettop(L); luaL_newmetatable(L, "__teliva_call_graph_depth"); int cgt = lua_gettop(L); lua_pushnil(L); while (lua_next(L, cgt) != 0) { lua_pop(L, 1); /* old value */ lua_pushvalue(L, -1); /* duplicate key */ lua_pushnil(L); /* new value */ lua_settable(L, cgt); /* one copy of key left for lua_next */ } lua_pop(L, 1); assert(lua_gettop(L) == oldtop); } /*** Permissions */ /* Perform privilege calculations in a whole other isolated context. */ lua_State* trustedL = NULL; static int isarg(lua_State* trustedL) { const char* arg = luaL_checkstring(trustedL, -1); lua_pushboolean(trustedL, any_equal(Argv, arg)); return 1; } static int inarg(lua_State* trustedL) { const char* arg = luaL_checkstring(trustedL, -1); lua_pushboolean(trustedL, any_starts_with(Argv, arg)); return 1; } static const luaL_Reg trusted_base_funcs[] = { {"isarg", isarg}, {"inarg", inarg}, }; void initialize_trustedL() { trustedL = luaL_newstate(); lua_gc(trustedL, LUA_GCSTOP, 0); /* stop collector during initialization */ luaL_openlibs(trustedL); luaL_register(trustedL, "_G", trusted_base_funcs); /* TODO: Should we include ncurses? How to debug policies? */ lua_gc(trustedL, LUA_GCRESTART, 0); } static const char* user_configuration_filename() { const char* home = getenv("HOME"); if (home == NULL) { endwin(); fprintf(stderr, "$HOME is not set; unclear where to save permissions.\n"); abort(); } static char config_filename[1024] = {0}; memset(config_filename, '\0', 1024); const char* config_home = getenv("XDG_CONFIG_HOME"); if (config_home == NULL) snprintf(config_filename, 1020, "%s/.teliva", home); else snprintf(config_filename, 1020, "%s/.teliva", config_home); return config_filename; } static int file_operation_permitted_manually(const char* filename, const char* mode) { int old_y, old_x; getyx(stdscr, old_y, old_x); attr_t old_attrs; short old_pair; attr_get(&old_attrs, &old_pair, NULL); attrset(A_NORMAL); mvaddstr(LINES-1, 0, ""); clrtoeol(); attrset(A_REVERSE); if (strncmp(mode, "r", /*strlen("r") + 1 for NULL*/ 2) == 0) mvprintw(LINES-1, 0, "open file \"%s\" for reading? ", filename); else mvprintw(LINES-1, 0, "open file \"%s\" for reading and writing? ", filename); attrset(A_NORMAL); int response = getch(); attr_set(old_attrs, old_pair, NULL); mvaddstr(old_y, old_x, ""); return response == 'y'; } static int file_operation_permitted_automatically(const char* filename, const char* mode) { int oldtop = lua_gettop(trustedL); lua_getglobal(trustedL, "file_operation_permitted"); lua_pushstring(trustedL, filename); lua_pushboolean(trustedL, strncmp(mode, "r", /*strlen("r") + 1 for NULL*/ 2) != 0); if (lua_pcall(trustedL, 2 /*args*/, 1 /*result*/, /*errfunc*/0)) { /* TODO: error handling. Or should we use errfunc above? */ } if (!lua_isboolean(trustedL, -1)) { endwin(); printf("Sorry, there's an error in permissions for this image.\n"); printf("Delete '%s' or try editing it by hand.\n", user_configuration_filename()); exit(1); } int should_allow = lua_toboolean(trustedL, -1); lua_settop(trustedL, oldtop); return should_allow; } int file_operation_permitted(const char* filename, const char* mode) { if (ask_for_permission_on_every_file_operation) return file_operation_permitted_manually(filename, mode); else return file_operation_permitted_automatically(filename, mode); } static void permissions_menu() { attrset(A_REVERSE); for (int x = 0; x < COLS; ++x) mvaddch(LINES-1, x, ' '); attrset(A_NORMAL); menu_column = 2; draw_menu_item("^x", "go back"); if (!ask_for_permission_on_every_file_operation) { draw_menu_item("^f", "edit file permissions"); draw_menu_item("^a", "ask for permission on every file operation"); } else { draw_menu_item("^a", "stop asking for permission on every file operation"); } draw_menu_item("^n", "toggle network permissions"); attrset(A_NORMAL); } void characterize_file_operations_predicate() { static const char* test_filenames[] = { "foo", "/foo", "../foo", NULL }; static const char* test_modes[] = { "r", "r+", "w", "w+", "a", "a+", NULL }; int num_attempts = 0; int num_rejections = 0; int num_errors = 0; for (const char** test_filename = test_filenames; *test_filename; ++test_filename) { for (const char** test_mode = test_modes; *test_mode; ++test_mode) { lua_getglobal(trustedL, "file_operation_permitted"); lua_pushstring(trustedL, *test_filename); lua_pushstring(trustedL, *test_mode); if (lua_pcall(trustedL, 2 /*args*/, 1 /*result*/, /*errfunc*/0)) { /* TODO: error handling. Or should we use errfunc above? */ } ++num_attempts; if (!lua_isboolean(trustedL, -1)) { ++num_errors; } else { if (!lua_toboolean(trustedL, -1)) ++num_rejections; } } } if (num_errors > 0) { attron(COLOR_PAIR(COLOR_PAIR_ERROR)); addstr(" Throws errors some of the time. You should fix them before moving on. "); attroff(COLOR_PAIR(COLOR_PAIR_ERROR)); } else if (strcmp("return false", trim(file_operations_predicate_body)) == 0) { attron(COLOR_PAIR(COLOR_PAIR_SAFE)); addstr("● Rejects all file operations."); attroff(COLOR_PAIR(COLOR_PAIR_SAFE)); } else if (strcmp("return true", trim(file_operations_predicate_body)) == 0) { attron(COLOR_PAIR(COLOR_PAIR_WARN)); addstr("◯ Allows all file operations."); attroff(COLOR_PAIR(COLOR_PAIR_WARN)); } else { static const char* statuses[5] = { "◯ Weakly suspected to allow all file operations.", "◔ Weakly suspected to allow most file operations.", "◑ Weakly suspected to allow many file operations.", "◕ Weakly suspected to reject most file operations.", "● Weakly suspected to reject all file operations.", }; attron(COLOR_PAIR(COLOR_PAIR_FADE)); int frac = (float)num_rejections/num_attempts*4; addstr(statuses[frac]); attroff(COLOR_PAIR(COLOR_PAIR_FADE)); } } static void render_permissions_screen() { clear(); attrset(A_BOLD); mvaddstr(1, 5, "Permissions: What sensitive operations this app is allowed to perform"); mvaddstr(2, 5, "🚧 Be very careful granting permissions 🚧"); attrset(A_NORMAL); int y = 7; mvaddstr(y, 5, "File operations"); if (!ask_for_permission_on_every_file_operation) { mvaddstr(y, 30, "function file_operation_permitted(filename, is_write)"); y = render_wrapped_text(y+1, 32, COLS-5, file_operations_predicate_body); mvaddstr(y, 30, "end"); y++; mvaddstr(y, 30, ""); characterize_file_operations_predicate(); } else { attron(COLOR_PAIR(COLOR_PAIR_WARN)); attron(A_REVERSE); mvaddstr(y, 30, " always ask "); attroff(A_REVERSE); attroff(COLOR_PAIR(COLOR_PAIR_WARN)); } y += 2; int net_colors = net_operations_permitted ? COLOR_PAIR_WARN : COLOR_PAIR_SAFE; mvaddstr(y, 5, "Network operations"); attron(COLOR_PAIR(net_colors)); attron(A_REVERSE); switch (net_colors) { case COLOR_PAIR_SAFE: mvaddstr(y, 30, " never "); break; case COLOR_PAIR_WARN: mvaddstr(y, 30, " always "); break; case COLOR_PAIR_RISK: mvaddstr(y, 30, " "); break; default: abort(); } y++; attroff(A_REVERSE); attroff(COLOR_PAIR(net_colors)); mvaddstr(y, 30, "(No nuance available for network operations.)"); if (!ask_for_permission_on_every_file_operation) { int file_operations_safe = strcmp("return false", trim(file_operations_predicate_body)) == 0; int net_operations_safe = (net_operations_permitted == 0); int file_operations_unsafe = strcmp("return true", trim(file_operations_predicate_body)) == 0; int net_operations_unsafe = (net_operations_permitted != 0); if (file_operations_safe && net_operations_safe) { attron(COLOR_PAIR(COLOR_PAIR_SAFE)); mvaddstr(5, 5, "This app can't access private data or communicate with other computers."); attroff(COLOR_PAIR(COLOR_PAIR_SAFE)); } else if (file_operations_safe || net_operations_safe) { attron(COLOR_PAIR(COLOR_PAIR_WARN)); if (net_operations_safe) { mvaddstr(5, 5, "This app can access private data, but they can't leave this computer."); } else { mvaddstr(5, 5, "This app can communicate with other computers, but can't access private data."); } attroff(COLOR_PAIR(COLOR_PAIR_WARN)); } else if (file_operations_unsafe && net_operations_unsafe) { attron(COLOR_PAIR(COLOR_PAIR_RISK)); // idea: include pentagram emoji. But it isn't widely supported yet on Linux. mvaddstr(5, 5, "😈 ⚠️ Teliva can't protect you if this app does something sketchy. Consider restricting permissions. ⚠️ 😈"); attroff(COLOR_PAIR(COLOR_PAIR_RISK)); } else { attron(COLOR_PAIR(COLOR_PAIR_RISK)); mvaddstr(5, 5, "🦮 🙈 Teliva can't tell how much it's protecting you. Consider simplifying permissions."); attroff(COLOR_PAIR(COLOR_PAIR_RISK)); } } else { // ask_for_permission_on_every_file_operation is true if (net_operations_permitted == 0) { attron(COLOR_PAIR(COLOR_PAIR_WARN)); mvaddstr(5, 5, "You're manually managing file permissions, but they can't leave this computer."); attroff(COLOR_PAIR(COLOR_PAIR_WARN)); } else { attron(COLOR_PAIR(COLOR_PAIR_WARN)); mvaddstr(5, 5, "You're manually managing file permissions, and the app can access the network. Watch out for fatigue."); attroff(COLOR_PAIR(COLOR_PAIR_WARN)); //? attron(COLOR_PAIR(COLOR_PAIR_RISK)); //? mvaddstr(5, 5, "😈 ⚠️ Manually managing file permissions on a networked app is a losing enterprise. ⚠️ 😈"); //? attroff(COLOR_PAIR(COLOR_PAIR_RISK)); } } permissions_menu(); refresh(); } /* Try running the function to test for errors. If code has an error, leave it * on the stack and return non-zero */ int validate_file_operations_predicate() { lua_getglobal(trustedL, "file_operation_permitted"); lua_pushstring(trustedL, "filename"); lua_pushstring(trustedL, "r"); /* open mode */ if (lua_pcall(trustedL, 2 /*args*/, 1 /*result*/, /*errfunc*/0)) { /* TODO: error handling. Or should we use errfunc above? */ } int status = 1; if (lua_isboolean(trustedL, -1)) { lua_pop(trustedL, 1); status = 0; } return status; } static int load_file_operations_predicate(const char* body) { char buffer[1024] = {0}; strcpy(buffer, "function file_operation_permitted(filename, is_write)\n"); strncat(buffer, body, 1020); if (buffer[strlen(buffer)-1] != '\n') strncat(buffer, "\n", 1020); strncat(buffer, "end\n", 1020); return luaL_loadbuffer(trustedL, buffer, strlen(buffer), "file_operation_permitted") || docall(trustedL, 0, 1) || validate_file_operations_predicate(); } extern void editFilePermissions(char* filename); extern void resumeFilePermissionsEdit(); static void edit_file_operations_predicate_body() { static char file_operations_predicate_body_buffer[512]; /* save to disk */ char outfilename[] = "teliva_file_operations_predicate_body_XXXXXX"; int outfd = mkstemp(outfilename); if (outfd == -1) { endwin(); perror("edit_file_operations_predicate_body: error in creating temporary file"); abort(); } FILE* out = fdopen(outfd, "w"); assert(out != NULL); fprintf(out, "%s", file_operations_predicate_body); fclose(out); rename(outfilename, "teliva_file_operations_predicate_body"); Previous_error = ""; editFilePermissions("teliva_file_operations_predicate_body"); // error handling assert(trustedL); int oldtop = lua_gettop(trustedL); while (1) { int status; memset(file_operations_predicate_body_buffer, '\0', 512); FILE* in = fopen("teliva_file_operations_predicate_body", "r"); fread(file_operations_predicate_body_buffer, 500, 1, in); /* TODO: error message if file too large */ fclose(in); status = load_file_operations_predicate(file_operations_predicate_body_buffer); if (status == 0 || lua_isnil(trustedL, -1)) break; Previous_error = lua_tostring(trustedL, -1); if (Previous_error == NULL) Previous_error = "(error object is not a string)"; resumeFilePermissionsEdit(); lua_pop(trustedL, 1); } file_operations_predicate_body = file_operations_predicate_body_buffer; if (lua_gettop(trustedL) != oldtop) { endwin(); printf("edit_file_operations_predicate_body: memory leak %d -> %d\n", oldtop, lua_gettop(trustedL)); exit(1); } } void print_file_permission_suggestions(int row) { mvaddstr(row++, 0, "-- Some ideas:"); mvaddstr(row++, 0, "-- * restrict access to a single file: return filename == 'foo'"); mvaddstr(row++, 0, "-- * restrict to reading only: return is_write == false"); mvaddstr(row++, 0, "-- * restrict to files with a fixed prefix: return string.find(filename, 'foo') == 1"); mvaddstr(row++, 0, "-- * restrict to files with a fixed extension: return filename:sub(-4) == '.txt'"); mvaddstr(row++, 0, "-- * restrict to files under some directory: return string.find(filename, 'foo/') == 1"); mvaddstr(row++, 0, "-- * restrict access only to files specified on commandline: return isarg(filename)"); mvaddstr(row++, 0, "-- * restrict access only to paths under directories specified on commandline: return inargs(filename)"); mvaddstr(row++, 0, "--"); mvaddstr(row++, 0, "-- Each of these has benefits and drawbacks."); } static void permissions_view() { while (true) { render_permissions_screen(); int c = getch(); switch (c) { case CTRL_X: return; case CTRL_F: if (!ask_for_permission_on_every_file_operation) edit_file_operations_predicate_body(); break; case CTRL_A: ask_for_permission_on_every_file_operation = !ask_for_permission_on_every_file_operation; break; case CTRL_N: net_operations_permitted = !net_operations_permitted; break; } } } static void save_permissions_to_user_configuration(lua_State* L) { const char* rcfilename = user_configuration_filename(); FILE* in = fopen(rcfilename, "r"); /* can be NULL when rcfile doesn't exist */ char outfilename[] = "telivarc_XXXXXX"; int outfd = mkstemp(outfilename); if (outfd == -1) { endwin(); perror("error in creating temporary file"); abort(); } FILE* out = fdopen(outfd, "w"); assert(out != NULL); /* read entries from rcfilename and write them to outfilename. If image name * matches the current Image_name, ignore. */ int oldtop = lua_gettop(L); while (in && !feof(in)) { teliva_load_definition(L, in); if (lua_isnil(L, -1)) break; lua_getfield(L, -1, "image_name"); const char* image_name = lua_tostring(L, -1); if (strcmp(image_name, Image_name) != 0) { fprintf(out, "- image_name: %s\n", image_name); fprintf(out, " file_operations_predicate_body:\n"); lua_getfield(L, -2, "file_operations_predicate_body"); if (!lua_isnil(L, -1)) emit_multiline_string(out, lua_tostring(L, -1)); lua_pop(L, 1); /* file_operations_predicate_body */ lua_getfield(L, -2, "ask_for_permission_on_every_file_operation"); fprintf(out, " ask_for_permission_on_every_file_operation: %s\n", lua_tostring(L, -1)); lua_pop(L, 1); /* ask_for_permission_on_every_file_operation */ lua_getfield(L, -2, "net_operations_permitted"); fprintf(out, " net_operations_permitted: %s\n", lua_tostring(L, -1)); lua_pop(L, 1); /* net_operations_permitted */ } lua_pop(L, 1); /* image_name */ } lua_settop(L, oldtop); fprintf(out, "- image_name: %s\n", Image_name); fprintf(out, " file_operations_predicate_body:\n"); assert(file_operations_predicate_body); emit_multiline_string(out, file_operations_predicate_body); fprintf(out, " ask_for_permission_on_every_file_operation: %d\n", ask_for_permission_on_every_file_operation); fprintf(out, " net_operations_permitted: %d\n", net_operations_permitted); fclose(out); if (in) fclose(in); rename(outfilename, rcfilename); } static void load_permissions_from_user_configuration(lua_State* L) { static char file_operations_predicate_body_buffer[512]; initialize_trustedL(); file_operations_predicate_body = default_file_operations_predicate_body; int status = load_file_operations_predicate(file_operations_predicate_body); if (status != 0 && lua_isnil(trustedL, -1)) { endwin(); printf("can't load default file operations predicate_body\n"); exit(1); } const char* rcfilename = user_configuration_filename(); FILE* in = fopen(rcfilename, "r"); if (in == NULL) return; file_operations_predicate_body = default_file_operations_predicate_body; assert(file_operations_predicate_body); /* read entries from rcfilename and look for a match with the current * Image_name. */ int oldtop = lua_gettop(L); while (!feof(in)) { teliva_load_definition(L, in); if (lua_isnil(L, -1)) break; lua_getfield(L, -1, "image_name"); const char* image_name = lua_tostring(L, -1); if (strcmp(image_name, Image_name) == 0) { lua_getfield(L, -2, "file_operations_predicate_body"); if (!lua_isnil(L, -1)) { memset(file_operations_predicate_body_buffer, '\0', 512); strncpy(file_operations_predicate_body_buffer, lua_tostring(L, -1), 500); file_operations_predicate_body = file_operations_predicate_body_buffer; } lua_pop(L, 1); /* file_operations_predicate_body */ lua_getfield(L, -2, "ask_for_permission_on_every_file_operation"); ask_for_permission_on_every_file_operation = lua_tointeger(L, -1); lua_pop(L, 1); /* ask_for_permission_on_every_file_operation */ lua_getfield(L, -2, "net_operations_permitted"); net_operations_permitted = lua_tointeger(L, -1); lua_pop(L, 1); /* net_operations_permitted */ } lua_pop(L, 1); /* image_name */ } lua_settop(L, oldtop); fclose(in); /* trusted section */ assert(file_operations_predicate_body); status = load_file_operations_predicate(file_operations_predicate_body); if (status == 0 || lua_isnil(trustedL, -1)) return; /* TODO: more graceful error handling */ endwin(); printf("error in loading file operations predicate_body from %s\n", rcfilename); exit(1); } void permissions_mode(lua_State* L) { assume_default_colors(COLOR_FOREGROUND, COLOR_BACKGROUND); init_pair(COLOR_PAIR_NORMAL, COLOR_FOREGROUND, COLOR_BACKGROUND); init_pair(COLOR_PAIR_SELECTABLE, COLOR_SELECTABLE_FOREGROUND, COLOR_SELECTABLE_BACKGROUND); init_pair(COLOR_PAIR_FADE, COLOR_FADE, COLOR_BACKGROUND); init_pair(COLOR_PAIR_MENU_ALTERNATE, COLOR_MENU_ALTERNATE, COLOR_BACKGROUND); init_pair(COLOR_PAIR_LUA_COMMENT, COLOR_LUA_COMMENT, COLOR_BACKGROUND); init_pair(COLOR_PAIR_LUA_KEYWORD, COLOR_LUA_KEYWORD, COLOR_BACKGROUND); init_pair(COLOR_PAIR_LUA_CONSTANT, COLOR_LUA_CONSTANT, COLOR_BACKGROUND); init_pair(COLOR_PAIR_MATCH, COLOR_MATCH_FOREGROUND, COLOR_MATCH_BACKGROUND); init_pair(COLOR_PAIR_ERROR, COLOR_ERROR_FOREGROUND, COLOR_ERROR_BACKGROUND); /* permissions colors slightly different than in the menu */ init_pair(COLOR_PAIR_SAFE, COLOR_SAFE_NORMAL, COLOR_BACKGROUND); init_pair(COLOR_PAIR_WARN, COLOR_WARN_NORMAL, COLOR_BACKGROUND); init_pair(COLOR_PAIR_RISK, COLOR_RISK_NORMAL, COLOR_BACKGROUND); nodelay(stdscr, 0); /* always make getch() block in developer mode */ curs_set(1); /* always display cursor in developer mode */ permissions_view(); save_permissions_to_user_configuration(L); cleanup_curses(); execv(Argv[0], Argv); /* never returns */ } /*** (Audit) Events screen */ typedef struct { char* line; char* func; } AuditEvent; #define NAUDIT 8192 AuditEvent audit_event[NAUDIT]; int naudit = 0; int iaudit = 0; void append_to_audit_log(lua_State* L, const char* buffer) { lua_Debug ar; lua_getstack(L, 1, &ar); lua_getinfo(L, "n", &ar); if (!ar.name) return; audit_event[naudit].line = strdup(buffer); audit_event[naudit].func = strdup(ar.name); ++naudit; if (naudit >= NAUDIT) naudit = 0; if (naudit == iaudit) { ++iaudit; if (iaudit >= NAUDIT) iaudit = 0; } } static void events_menu() { attrset(A_REVERSE); for (int x = 0; x < COLS; ++x) mvaddch(LINES-1, x, ' '); attrset(A_NORMAL); menu_column = 2; draw_menu_item("^x", "go back"); draw_menu_item("Enter", "go to highlight"); attrset(A_NORMAL); } static void render_event(int i, int y, int cursor) { mvaddstr(y, 2, ""); if (i == cursor) draw_highlighted_definition_name(audit_event[i].func); else draw_definition_name(audit_event[i].func); mvaddstr(y, 16, audit_event[i].line); } static void render_events(int cursor) { clear(); attrset(A_BOLD); mvaddstr(1, 0, "Recent events"); attrset(A_NORMAL); if (iaudit == 0) { /* circular buffer might not be full */ for (int i = 0, y = 3; i < naudit; ++i, ++y) { if (i >= LINES-1) break; render_event(i, y, cursor); } } else { /* circular buffer guaranteed to be full */ for (int i = 0, y = 3; i < NAUDIT; ++i, ++y) { if (i >= LINES-1) break; render_event((iaudit+i)%NAUDIT, y, cursor); } } events_menu(); refresh(); } static const char* events_view() { int cursor = 0; while (true) { render_events(cursor); int c = getch(); switch (c) { case CTRL_X: return NULL; case KEY_UP: if (cursor > 0) --cursor; break; case KEY_DOWN: if (cursor < naudit-1) ++cursor; break; case ENTER: return audit_event[cursor].func; } } } /*** Main */ char* Image_name = NULL; extern void set_args (lua_State *L, char **argv, int n); extern void load_tlv(lua_State* L, char* filename); int load_image(lua_State* L, char** argv, int n) { int status; set_args(L, argv, n); /* parse and load file contents (teliva_program array) */ Image_name = argv[n]; load_tlv(L, Image_name); //? save_tlv(L, Image_name); // manual test; should always return identical result, modulo key order //? exit(1); status = load_definitions(L); if (status != 0) return 0; precompute_names_of_globals(L); /* run tests */ status = run_tests(L); if (status != 0) return report_in_developer_mode(L, status); /* clear stats from running tests */ clear_call_graph_depth(L); clear_caller(L); /* initialize permissions */ load_permissions_from_user_configuration(L); return 0; } ================================================ FILE: src/teliva.h ================================================ #ifndef __TELIVA_H__ #define __TELIVA_H__ /*** Some details for Teliva apps to be aware of. */ /* Some names for hotkeys beyond those provided by ncurses. */ /* TODO: expose these in the curses wrappers. */ enum KEY_ACTION { KEY_NULL = 0, CTRL_A = 1, CTRL_B = 2, CTRL_C = 3, CTRL_D = 4, CTRL_E = 5, CTRL_F = 6, CTRL_G = 7, CTRL_H = 8, TAB = 9, ENTER = 10, CTRL_K = 11, CTRL_L = 12, CTRL_N = 14, CTRL_P = 16, CTRL_Q = 17, CTRL_R = 18, CTRL_S = 19, CTRL_U = 21, CTRL_X = 24, CTRL_SLASH = 31, CTRL_UNDERSCORE = 31, DELETE = 127, }; /* Colors (experimental) * Primary goal here: Keep text readable regardless of OS, terminal emulator * and color scheme. Unfortunately I don't yet have a good answer, so this * approach may yet change. Current approach: * - Hardcode colors so that we can be sure we use legible combinations of * foreground and background. * - Use only the terminal palette in the range 16-255. * - Not all terminals may support more than 256 colors. (I'm not yet sure * everyone has even 256 colors. If you don't, please let me know: * http://akkartik.name/contact) * - Many terminals provide color schemes which give the ability to tweak * colors 0-15. This makes it hard to assume specific combinations are * legible. I'm hoping most terminal emulators don't tend to encourage * messing with colors 16-255. (Please let me know if you know of * counter-examples.) * * For now, you have to edit these values if you want to adjust colors in the * editing environment. Check out https://www.robmeerman.co.uk/unix/256colours * for a map of available colors. */ /* Toggle between a few color schemes */ #define COLOR_SCHEME 0 #if COLOR_SCHEME == 0 /* Light color scheme. */ enum color { COLOR_FOREGROUND = 238, /* almost black */ COLOR_BACKGROUND = 253, /* almost white */ COLOR_FADE = 244, /* closer to background */ COLOR_MENU_ALTERNATE = 248, COLOR_SELECTABLE_FOREGROUND = 238, COLOR_SELECTABLE_BACKGROUND = 250, COLOR_ERROR_FOREGROUND = COLOR_BACKGROUND, COLOR_ERROR_BACKGROUND = 124, /* deep red */ COLOR_SAFE_NORMAL = 28, /* green */ COLOR_SAFE_REVERSE = 46, /* green */ COLOR_WARN_NORMAL = 208, /* orange */ COLOR_WARN_REVERSE = 208, /* orange */ COLOR_RISK_NORMAL = 196, /* red */ COLOR_RISK_REVERSE = 196, /* red */ COLOR_LUA_COMMENT = 27, /* blue */ COLOR_LUA_KEYWORD = 172, /* orange */ COLOR_LUA_CONSTANT = 31, /* cyan */ COLOR_MATCH_FOREGROUND = COLOR_BACKGROUND, COLOR_MATCH_BACKGROUND = 28, /* green */ }; #elif COLOR_SCHEME == 1 /* Dark color scheme. */ enum color { COLOR_FOREGROUND = 253, /* almost white */ COLOR_BACKGROUND = 238, /* almost black */ COLOR_FADE = 244, /* closer to background */ COLOR_MENU_ALTERNATE = 244, COLOR_SELECTABLE_FOREGROUND = 238, COLOR_SELECTABLE_BACKGROUND = 250, COLOR_ERROR_FOREGROUND = COLOR_FOREGROUND, COLOR_ERROR_BACKGROUND = 124, /* deep red */ COLOR_SAFE_NORMAL = 46, /* green */ COLOR_SAFE_REVERSE = 28, /* green */ COLOR_WARN_NORMAL = 208, /* orange */ COLOR_WARN_REVERSE = 130, /* orange */ COLOR_RISK_NORMAL = 196, /* red */ COLOR_RISK_REVERSE = 196, /* red */ COLOR_LUA_COMMENT = 39, /* blue */ COLOR_LUA_KEYWORD = 172, /* orange */ COLOR_LUA_CONSTANT = 37, /* cyan */ COLOR_MATCH_FOREGROUND = COLOR_BACKGROUND, COLOR_MATCH_BACKGROUND = 28, /* green */ }; #elif COLOR_SCHEME == 2 /* Solarized dark. */ enum color { COLOR_FOREGROUND = 250, /* almost white */ COLOR_BACKGROUND = 24, /* dark blue-green */ COLOR_FADE = 246, /* closer to background */ COLOR_MENU_ALTERNATE = 244, COLOR_SELECTABLE_FOREGROUND = 250, COLOR_SELECTABLE_BACKGROUND = 31, COLOR_ERROR_FOREGROUND = 250, COLOR_ERROR_BACKGROUND = 124, /* deep red */ COLOR_SAFE_NORMAL = 46, /* green */ COLOR_SAFE_REVERSE = 28, /* green */ COLOR_WARN_NORMAL = 208, /* orange */ COLOR_WARN_REVERSE = 130, /* orange */ COLOR_RISK_NORMAL = 201, /* red */ COLOR_RISK_REVERSE = 196, /* red */ COLOR_LUA_COMMENT = 45, /* light blue */ COLOR_LUA_KEYWORD = 172, /* orange */ COLOR_LUA_CONSTANT = 37, /* cyan */ COLOR_MATCH_FOREGROUND = COLOR_FOREGROUND, COLOR_MATCH_BACKGROUND = 125, /* magenta */ }; #endif enum color_pair { COLOR_PAIR_NORMAL = 0, COLOR_PAIR_SELECTABLE = 1, COLOR_PAIR_FADE = 2, COLOR_PAIR_MENU_ALTERNATE = 3, COLOR_PAIR_LUA_COMMENT = 4, COLOR_PAIR_LUA_KEYWORD = 5, COLOR_PAIR_LUA_CONSTANT = 6, COLOR_PAIR_MATCH = 7, COLOR_PAIR_SAFE = 251, /* reserved for teliva; apps shouldn't use it */ COLOR_PAIR_WARN = 252, /* reserved for teliva; apps shouldn't use it */ COLOR_PAIR_RISK = 253, /* reserved for teliva; apps shouldn't use it */ COLOR_PAIR_MENU = 254, /* reserved for teliva; apps shouldn't use it */ COLOR_PAIR_ERROR = 255, /* reserved for teliva; apps shouldn't use it */ }; /*** C Interface */ /* Each category of primitives below shows a few options from high to low * levels of abstraction. * (Lower levels aren't complete or well-designed, just what code outside * teliva.c needs.) */ /* Integrate with Lua VM */ extern char** Argv; extern char* Previous_message; extern int load_image(lua_State* L, char** argv, int n); extern void developer_mode(lua_State* L); extern void permissions_mode(lua_State* L); extern void load_editor_buffer_to_current_definition_in_image(lua_State* L); extern int load_editor_buffer_to_current_definition_in_image_and_reload(lua_State* L); extern void save_to_current_definition_and_editor_buffer(lua_State* L, const char* definition); extern void save_editor_state(int rowoff, int coloff, int cy, int cx); int editor_view_in_progress(lua_State* L); extern void save_call_graph_depth(lua_State* L, int depth, const char* name); extern void draw_current_definition_name_and_callers(lua_State* L); extern void append_to_audit_log(lua_State* L, const char* buffer); /* Standard UI elements */ extern void render_trusted_teliva_data(lua_State* L); extern void draw_menu_item(const char* key, const char* name); extern void draw_string_on_menu(const char* s); extern int menu_column; extern const char* character_name(char c); /* Error reporting */ extern const char* Previous_error; extern int report_in_developer_mode(lua_State* L, int status); extern void render_previous_error(void); /* Permissions checking */ extern int ask_for_permission_on_every_file_operation; extern int file_operation_permitted(const char* filename, const char* mode); extern int net_operations_permitted; /* Misc */ extern int starts_with(const char* s, const char* prefix); extern int contains(const char* s, const char* sub); #endif ================================================ FILE: src/tlv.c ================================================ #include #ifdef __NetBSD__ #include #else #include #endif #include #include #include #include #include "lua.h" #include "lauxlib.h" #include "tlv.h" /* If you encounter assertion failures in this file and _didn't_ manually edit * it, lease report the .tlv file that caused them: http://akkartik.name/contact. * * Manually edited files can have cryptic errors. Teliva's first priority is * to be secure, so it requires a fairly rigid file format and errors out if * things are even slightly amiss. */ /* This code is surprisingly hairy. Estimate of buffer overflows: 2. */ static void teliva_load_multiline_string(lua_State* L, FILE* in, char* line, int capacity) { luaL_Buffer b; luaL_buffinit(L, &b); int expected_indent = -1; while (1) { if (feof(in)) break; char c = fgetc(in); ungetc(c, in); if (c != ' ') { /* new definition; signal end to caller without reading a new line */ strcpy(line, "-\n"); break; } memset(line, '\0', capacity); if (fgets(line, capacity, in) == NULL) break; /* eof */ int max = strlen(line); assert(line[max-1] == '\n'); int indent = 0; while (indent < max-1 && line[indent] == ' ') ++indent; if (line[indent] != '>') break; /* new key/value pair in definition */ if (expected_indent == -1) expected_indent = indent; else assert(expected_indent == indent); int start = indent+1; /* skip '>' */ luaL_addstring(&b, &line[start]); /* guaranteed to at least be null */ } luaL_pushresult(&b); /* final state of line goes out into the world */ } /* leave a single table on stack containing the next top-level definition from the file */ void teliva_load_definition(lua_State* L, FILE* in) { lua_newtable(L); int def_idx = lua_gettop(L); char line[1024] = {'\0'}; do { if (feof(in) || fgets(line, 1024, in) == NULL) { lua_pushnil(L); return; } } while (line[0] == '#'); /* comment at start of file */ assert(line[strlen(line)-1] == '\n'); do { assert(line[0] == '-' || line[0] == ' '); assert(line[1] == ' '); /* key/value pair always indented at 0, never empty, unambiguously not a multiline string */ char key[512] = {'\0'}; char value[1024] = {'\0'}; assert(line[2] != ' '); assert(line[2] != '>'); assert(line[2] != '\n'); assert(line[2] != '\0'); memset(key, 0, 512); memset(value, 0, 1024); sscanf(line+2, "%s%s", key, value); assert(key[strlen(key)-1] == ':'); key[strlen(key)-1] = '\0'; lua_pushstring(L, key); if (value[0] != '\0') { lua_pushstring(L, value); /* value string on same line */ char c = fgetc(in); ungetc(c, in); if (c == '-') { strcpy(line, "-\n"); } else { memset(line, '\0', 1024); fgets(line, 1024, in); } } else { teliva_load_multiline_string(L, in, line, 1024); /* load from later lines */ } lua_settable(L, def_idx); } while (line[0] == ' '); } void load_tlv(lua_State* L, char* filename) { lua_newtable(L); int history_array = lua_gettop(L); FILE* in = fopen(filename, "r"); if (in == NULL) { endwin(); fprintf(stderr, "no such file\n"); exit(1); } for (int i = 1; !feof(in); ++i) { teliva_load_definition(L, in); if (lua_isnil(L, -1)) break; lua_rawseti(L, history_array, i); } fclose(in); lua_setglobal(L, "teliva_program"); } void emit_multiline_string(FILE* out, const char* value) { fprintf(out, " >"); for (const char* curr = value; *curr != '\0'; ++curr) { if (*curr == '\n' && *(curr+1) != '\0') fprintf(out, "\n >"); else fprintf(out, "%c", *curr); } } static const char* special_history_keys[] = { "__teliva_timestamp", "__teliva_note", "__teliva_undo", NULL, }; /* save key and its value at top of stack to out * no stack side effects */ static void save_tlv_key(lua_State* L, const char* key, FILE* out) { if (strcmp(key, "__teliva_undo") == 0) { fprintf(out, "%s: %ld\n", key, lua_tointeger(L, -1)); return; } const char* value = lua_tostring(L, -1); if (strchr(value, ' ') || strchr(value, '\n')) { fprintf(out, "%s:\n", key); emit_multiline_string(out, value); } else { fprintf(out, "%s: %s\n", key, value); } } void save_tlv(lua_State* L, char* filename) { lua_getglobal(L, "teliva_program"); int history_array = lua_gettop(L); int history_array_size = luaL_getn(L, history_array); char outfilename[] = "teliva_out_XXXXXX"; int outfd = mkstemp(outfilename); if (outfd == -1) { endwin(); perror("save_tlv: error in creating temporary file"); abort(); } FILE *out = fdopen(outfd, "w"); fprintf(out, "# .tlv file generated by https://github.com/akkartik/teliva\n"); fprintf(out, "# You may edit it if you are careful; however, you may see cryptic errors if you\n"); fprintf(out, "# violate Teliva's assumptions.\n"); fprintf(out, "#\n"); fprintf(out, "# .tlv files are representations of Teliva programs. Teliva programs consist of\n"); fprintf(out, "# sequences of definitions. Each definition is a table of key/value pairs. Keys\n"); fprintf(out, "# and values are both strings.\n"); fprintf(out, "#\n"); fprintf(out, "# Lines in .tlv files always follow exactly one of the following forms:\n"); fprintf(out, "# - comment lines at the top of the file starting with '#' at column 0\n"); fprintf(out, "# - beginnings of definitions starting with '- ' at column 0, followed by a\n"); fprintf(out, "# key/value pair\n"); fprintf(out, "# - key/value pairs consisting of ' ' at column 0, containing either a\n"); fprintf(out, "# spaceless value on the same line, or a multi-line value\n"); fprintf(out, "# - multiline values indented by more than 2 spaces, starting with a '>'\n"); fprintf(out, "#\n"); fprintf(out, "# If these constraints are violated, Teliva may unceremoniously crash. Please\n"); fprintf(out, "# report bugs at http://akkartik.name/contact\n"); for (int i = 1; i <= history_array_size; ++i) { lua_rawgeti(L, history_array, i); int table = lua_gettop(L); int first = 1; // standardize order of special keys for (int k = 0; special_history_keys[k]; ++k) { lua_getfield(L, table, special_history_keys[k]); if (!lua_isnil(L, -1)) { if (first) fprintf(out, "- "); else fprintf(out, " "); first = 0; save_tlv_key(L, special_history_keys[k], out); } lua_pop(L, 1); } for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) { if (is_special_history_key(lua_tostring(L, -2))) continue; if (first) fprintf(out, "- "); else fprintf(out, " "); first = 0; save_tlv_key(L, lua_tostring(L, -2), out); } lua_pop(L, 1); } fclose(out); rename(outfilename, filename); lua_pop(L, 1); } int is_special_history_key(const char* key) { for (const char** curr = special_history_keys; *curr != NULL; ++curr) { if (strcmp(*curr, key) == 0) return 1; } return 0; } ================================================ FILE: src/tlv.h ================================================ #ifndef __TLV_H__ #define __TLV_H__ /* Helpers for working with the .tlv file format */ extern void teliva_load_definition (lua_State* L, FILE* in); int is_special_history_key(const char* key); extern void emit_multiline_string(FILE* out, const char* value); #endif ================================================ FILE: src/vimrc.vim ================================================ set tabstop=8 set softtabstop=2 set shiftwidth=2 ================================================ FILE: template.tlv ================================================ # .tlv file generated by https://github.com/akkartik/teliva # You may edit it if you are careful; however, you may see cryptic errors if you # violate Teliva's assumptions. # # .tlv files are representations of Teliva programs. Teliva programs consist of # sequences of definitions. Each definition is a table of key/value pairs. Keys # and values are both strings. # # Lines in .tlv files always follow exactly one of the following forms: # - comment lines at the top of the file starting with '#' at column 0 # - beginnings of definitions starting with '- ' at column 0, followed by a # key/value pair # - key/value pairs consisting of ' ' at column 0, containing either a # spaceless value on the same line, or a multi-line value # - multiline values indented by more than 2 spaces, starting with a '>' # # If these constraints are violated, Teliva may unceremoniously crash. Please # report bugs at http://akkartik.name/contact - __teliva_timestamp: original str_helpers: >-- some string helpers from http://lua-users.org/wiki/StringIndexing > >-- index characters using [] >getmetatable('').__index = function(str,i) > if type(i) == 'number' then > return str:sub(i,i) > else > return string[i] > end >end > >-- ranges using (), selected bytes using {} >getmetatable('').__call = function(str,i,j) > if type(i)~='table' then > return str:sub(i,j) > else > local t={} > for k,v in ipairs(i) do > t[k]=str:sub(v,v) > end > return table.concat(t) > end >end > >-- iterate over an ordered sequence >function q(x) > if type(x) == 'string' then > return x:gmatch('.') > else > return ipairs(x) > end >end > >-- insert within string >function string.insert(str1, str2, pos) > return str1:sub(1,pos)..str2..str1:sub(pos+1) >end > >function string.remove(s, pos) > return s:sub(1,pos-1)..s:sub(pos+1) >end > >function string.pos(s, sub) > return string.find(s, sub, 1, true) -- plain=true to disable regular expressions >end > >-- TODO: backport utf-8 support from Lua 5.3 - __teliva_timestamp: original debugy: >debugy = 5 - __teliva_timestamp: original dbg: >-- helper for debug by print; overlay debug information towards the right >-- reset debugy every time you refresh screen >function dbg(window, s) > local oldy = 0 > local oldx = 0 > oldy, oldx = window:getyx() > window:mvaddstr(debugy, 60, s) > debugy = debugy+1 > window:mvaddstr(oldy, oldx, '') >end - __teliva_timestamp: original check: >function check(x, msg) > if x then > Window:addch('.') > else > print('F - '..msg) > print(' '..str(x)..' is false/nil') > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original check_eq: >function check_eq(x, expected, msg) > if eq(x, expected) then > Window:addch('.') > else > print('F - '..msg) > print(' expected '..str(expected)..' but got '..str(x)) > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original eq: >function eq(a, b) > if type(a) ~= type(b) then return false end > if type(a) == 'table' then > if #a ~= #b then return false end > for k, v in pairs(a) do > if b[k] ~= v then > return false > end > end > for k, v in pairs(b) do > if a[k] ~= v then > return false > end > end > return true > end > return a == b >end - __teliva_timestamp: original str: >-- smarter tostring >-- slow; used only for debugging >function str(x) > if type(x) == 'table' then > local result = '' > result = result..#x..'{' > for k, v in pairs(x) do > result = result..str(k)..'='..str(v)..', ' > end > result = result..'}' > return result > elseif type(x) == 'string' then > return '"'..x..'"' > end > return tostring(x) >end - __teliva_timestamp: original find_index: >function find_index(arr, x) > for n, y in ipairs(arr) do > if x == y then > return n > end > end >end - __teliva_timestamp: original trim: >function trim(s) > return s:gsub('^%s*', ''):gsub('%s*$', '') >end - __teliva_timestamp: original split: >function split(s, d) > result = {} > for match in (s..d):gmatch("(.-)"..d) do > table.insert(result, match); > end > return result >end - __teliva_timestamp: original map: >-- only for arrays >function map(l, f) > result = {} > for _, x in ipairs(l) do > table.insert(result, f(x)) > end > return result >end - __teliva_timestamp: original reduce: >-- only for arrays >function reduce(l, f, init) > result = init > for _, x in ipairs(l) do > result = f(result, x) > end > return result >end - __teliva_timestamp: original filter: >function filter(h, f) > result = {} > for k, v in pairs(h) do > if f(k, v) then > result[k] = v > end > end > return result >end - __teliva_timestamp: original ifilter: >-- only for arrays >function ifilter(l, f) > result = {} > for _, x in ipairs(l) do > if f(x) then > table.insert(result, x) > end > end > return result >end - __teliva_timestamp: original sort_letters: >function sort_letters(s) > tmp = {} > for i=1,#s do > table.insert(tmp, s[i]) > end > table.sort(tmp) > local result = '' > for _, c in pairs(tmp) do > result = result..c > end > return result >end > >function test_sort_letters(s) > check_eq(sort_letters(''), '', 'test_sort_letters: empty') > check_eq(sort_letters('ba'), 'ab', 'test_sort_letters: non-empty') > check_eq(sort_letters('abba'), 'aabb', 'test_sort_letters: duplicates') >end - __teliva_timestamp: original count_letters: >-- TODO: handle unicode >function count_letters(s) > local result = {} > for i=1,s:len() do > local c = s[i] > if result[c] == nil then > result[c] = 1 > else > result[c] = result[c] + 1 > end > end > return result >end - __teliva_timestamp: original count: >-- turn an array of elements into a map from elements to their frequency >-- analogous to count_letters for non-strings >function count(a) > local result = {} > for i, v in ipairs(a) do > if result[v] == nil then > result[v] = 1 > else > result[v] = result[v] + 1 > end > end > return result >end - __teliva_timestamp: original union: >function union(a, b) > for k, v in pairs(b) do > a[k] = v > end > return a >end - __teliva_timestamp: original subtract: >-- set subtraction >function subtract(a, b) > for k, v in pairs(b) do > a[k] = nil > end > return a >end - __teliva_timestamp: original all: >-- universal quantifier on sets >function all(s, f) > for k, v in pairs(s) do > if not f(k, v) then > return false > end > end > return true >end - __teliva_timestamp: original to_array: >-- turn a set into an array >-- drops values >function to_array(h) > local result = {} > for k, _ in pairs(h) do > table.insert(result, k) > end > return result >end - __teliva_timestamp: original append: >-- concatenate list 'elems' into 'l', modifying 'l' in the process >function append(l, elems) > for i=1,#elems do > table.insert(l, elems[i]) > end >end - __teliva_timestamp: original prepend: >-- concatenate list 'elems' into the start of 'l', modifying 'l' in the process >function prepend(l, elems) > for i=1,#elems do > table.insert(l, i, elems[i]) > end >end - __teliva_timestamp: original all_but: >function all_but(x, idx) > if type(x) == 'table' then > local result = {} > for i, elem in ipairs(x) do > if i ~= idx then > table.insert(result,elem) > end > end > return result > elseif type(x) == 'string' then > if idx < 1 then return x:sub(1) end > return x:sub(1, idx-1) .. x:sub(idx+1) > else > error('all_but: unsupported type '..type(x)) > end >end > >function test_all_but() > check_eq(all_but('', 0), '', 'all_but: empty') > check_eq(all_but('abc', 0), 'abc', 'all_but: invalid low index') > check_eq(all_but('abc', 4), 'abc', 'all_but: invalid high index') > check_eq(all_but('abc', 1), 'bc', 'all_but: first index') > check_eq(all_but('abc', 3), 'ab', 'all_but: final index') > check_eq(all_but('abc', 2), 'ac', 'all_but: middle index') >end - __teliva_timestamp: original set: >function set(l) > local result = {} > for i, elem in ipairs(l) do > result[elem] = true > end > return result >end - __teliva_timestamp: original set_eq: >function set_eq(l1, l2) > return eq(set(l1), set(l2)) >end > >function test_set_eq() > check(set_eq({1}, {1}), 'set_eq: identical') > check(not set_eq({1, 2}, {1, 3}), 'set_eq: different') > check(set_eq({1, 2}, {2, 1}), 'set_eq: order') > check(set_eq({1, 2, 2}, {2, 1}), 'set_eq: duplicates') >end - __teliva_timestamp: original clear: >function clear(lines) > while #lines > 0 do > table.remove(lines) > end >end - __teliva_timestamp: original zap: >function zap(target, src) > clear(target) > append(target, src) >end - __teliva_timestamp: original mfactorial: >-- memoized version of factorial >-- doesn't memoize recursive calls, but may be good enough >mfactorial = memo1(factorial) - __teliva_timestamp: original factorial: >function factorial(n) > local result = 1 > for i=1,n do > result = result*i > end > return result >end - __teliva_timestamp: original memo1: >-- a higher-order function that takes a function of a single arg >-- (that never returns nil) >-- and returns a memoized version of it >function memo1(f) > local memo = {} > return function(x) > if memo[x] == nil then > memo[x] = f(x) > end > return memo[x] > end >end > >-- mfactorial doesn't seem noticeably faster >function test_memo1() > for i=0,30 do > check_eq(mfactorial(i), factorial(i), 'memo1 over factorial: '..str(i)) > end >end - __teliva_timestamp: original num_permutations: >-- number of permutations of n distinct objects, taken r at a time >function num_permutations(n, r) > return factorial(n)/factorial(n-r) >end > >-- mfactorial doesn't seem noticeably faster >function test_memo1() > for i=0,30 do > for j=0,i do > check_eq(num_permutations(i, j), mfactorial(i)/mfactorial(i-j), 'num_permutations memoizes: '..str(i)..'P'..str(j)) > end > end >end - __teliva_timestamp: original menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = {} - __teliva_timestamp: original Window: >Window = curses.stdscr() - __teliva_timestamp: original window: >-- constructor for fake screen and window >-- call it like this: >-- local w = window{ >-- kbd=kbd('abc'), >-- scr=scr{h=5, w=4}, >-- } >-- eventually it'll do everything a real ncurses window can >function window(h) > h.__index = h > setmetatable(h, h) > h.__index = function(table, key) > return rawget(h, key) > end > h.attrset = function(self, x) > self.scr.attrs = x > end > h.attron = function(self, x) > -- currently same as attrset since Lua 5.1 doesn't have bitwise operators > -- doesn't support multiple attrs at once >-- local old = self.scr.attrs >-- self.scr.attrs = old|x > self.scr.attrs = x > end > h.attroff = function(self, x) > -- currently borked since Lua 5.1 doesn't have bitwise operators > -- doesn't support multiple attrs at once >-- local old = self.scr.attrs >-- self.scr.attrs = old & (~x) > self.scr.attrs = curses.A_NORMAL > end > h.getch = function(self) > local c = table.remove(h.kbd, 1) > if c == nil then return c end > return string.byte(c) -- for verisimilitude with ncurses > end > h.addch = function(self, c) > local scr = self.scr > if c == '\n' then > scr.cursy = scr.cursy+1 > scr.cursx = 0 > return > end > if scr.cursy <= scr.h then > scr[scr.cursy][scr.cursx] = {data=c, attrs=scr.attrs} > scr.cursx = scr.cursx+1 > if scr.cursx > scr.w then > scr.cursy = scr.cursy+1 > scr.cursx = 1 > end > end > end > h.addstr = function(self, s) > for i=1,s:len() do > self:addch(s[i]) > end > end > h.mvaddch = function(self, y, x, c) > self.scr.cursy = y > self.scr.cursx = x > self:addch(c) > end > h.mvaddstr = function(self, y, x, s) > self.scr.cursy = y > self.scr.cursx = x > self:addstr(s) > end > h.clear = function(self) > clear_scr(self.scr) > end > h.refresh = function(self) > -- nothing > end > return h >end - __teliva_timestamp: original kbd: >function kbd(keys) > local result = {} > for i=1,keys:len() do > table.insert(result, keys[i]) > end > return result >end - __teliva_timestamp: original scr: >function scr(props) > props.cursx = 1 > props.cursy = 1 > clear_scr(props) > return props >end - __teliva_timestamp: original clear_scr: >function clear_scr(props) > props.cursy = 1 > props.cursx = 1 > for y=1,props.h do > props[y] = {} > for x=1,props.w do > props[y][x] = {data=' ', attrs=curses.A_NORMAL} > end > end > return props >end - __teliva_timestamp: original check_screen: >function check_screen(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > check_eq(window.scr[y][x].data, contents[i], message..'/'..y..','..x) > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end > >-- putting it all together, an example test of both keyboard and screen >function test_check_screen() > local lines = { > c='123', > d='234', > a='345', > b='456', > } > local w = window{ > kbd=kbd('abc'), > scr=scr{h=3, w=5}, > } > local y = 1 > while true do > local b = w:getch() > if b == nil then break end > w:mvaddstr(y, 1, lines[string.char(b)]) > y = y+1 > end > check_screen(w, '345 '.. > '456 '.. > '123 ', > 'test_check_screen') >end - __teliva_timestamp: original check_reverse: >function check_reverse(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > if contents[i] ~= ' ' then > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & curses.A_REVERSE, message..'/'..y..','..x) > check_eq(window.scr[y][x].attrs, curses.A_REVERSE, message..'/'..y..','..x) > else > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & (~curses.A_REVERSE), message..'/'..y..','..x) > check(window.scr[y][x].attrs ~= curses.A_REVERSE, message..'/'..y..','..x) > end > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end - __teliva_timestamp: original check_bold: >function check_bold(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > if contents[i] ~= ' ' then > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & curses.A_BOLD, message..'/'..y..','..x) > check_eq(window.scr[y][x].attrs, curses.A_BOLD, message..'/'..y..','..x) > else > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & (~curses.A_BOLD), message..'/'..y..','..x) > check(window.scr[y][x].attrs ~= curses.A_BOLD, message..'/'..y..','..x) > end > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end - __teliva_timestamp: original check_color: >-- check which parts of a screen have the given color_pair >function check_color(window, cp, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > if contents[i] ~= ' ' then > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & curses.color_pair(cp), message..'/'..y..','..x) > check_eq(window.scr[y][x].attrs, curses.color_pair(cp), message..'/'..y..','..x) > else > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & (~curses.A_BOLD), message..'/'..y..','..x) > check(window.scr[y][x].attrs ~= curses.color_pair(cp), message..'/'..y..','..x) > end > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end - __teliva_timestamp: original sep: >-- horizontal separator >function sep(window) > local y, _ = window:getyx() > window:mvaddstr(y+1, 0, '') > local _, cols = window:getmaxyx() > for col=1,cols do > window:addstr('_') > end >end - __teliva_timestamp: original render: >function render(window) > window:clear() > -- draw stuff to screen here > window:attron(curses.A_BOLD) > window:mvaddstr(1, 5, "example app") > window:attrset(curses.A_NORMAL) > for i=0,15 do > window:attrset(curses.color_pair(i)) > window:mvaddstr(3+i, 5, "========================") > end > window:refresh() >end - __teliva_timestamp: original update: >function update(window) > local key = window:getch() > -- process key here >end - __teliva_timestamp: original init_colors: >function init_colors() > for i=0,7 do > curses.init_pair(i, i, -1) > end > curses.init_pair(8, 7, 0) > curses.init_pair(9, 7, 1) > curses.init_pair(10, 7, 2) > curses.init_pair(11, 7, 3) > curses.init_pair(12, 7, 4) > curses.init_pair(13, 7, 5) > curses.init_pair(14, 7, 6) > curses.init_pair(15, -1, 15) >end - __teliva_timestamp: original main: >function main() > init_colors() > > while true do > render(Window) > update(Window) > end >end - __teliva_timestamp: original doc:blurb: >To show a brief description of the app on the 'big picture' screen, put the text in a special buffer called 'doc:blurb'. > >You can also override the default big picture screen entirely by creating a buffer called 'doc:main'. ================================================ FILE: toot-toot.tlv ================================================ # .tlv file generated by https://github.com/akkartik/teliva # You may edit it if you are careful; however, you may see cryptic errors if you # violate Teliva's assumptions. # # .tlv files are representations of Teliva programs. Teliva programs consist of # sequences of definitions. Each definition is a table of key/value pairs. Keys # and values are both strings. # # Lines in .tlv files always follow exactly one of the following forms: # - comment lines at the top of the file starting with '#' at column 0 # - beginnings of definitions starting with '- ' at column 0, followed by a # key/value pair # - key/value pairs consisting of ' ' at column 0, containing either a # spaceless value on the same line, or a multi-line value # - multiline values indented by more than 2 spaces, starting with a '>' # # If these constraints are violated, Teliva may unceremoniously crash. Please # report bugs at http://akkartik.name/contact - __teliva_timestamp: original str_helpers: >-- some string helpers from http://lua-users.org/wiki/StringIndexing > >-- index characters using [] >getmetatable('').__index = function(str,i) > if type(i) == 'number' then > return str:sub(i,i) > else > return string[i] > end >end > >-- ranges using (), selected bytes using {} >getmetatable('').__call = function(str,i,j) > if type(i)~='table' then > return str:sub(i,j) > else > local t={} > for k,v in ipairs(i) do > t[k]=str:sub(v,v) > end > return table.concat(t) > end >end > >-- iterate over an ordered sequence >function q(x) > if type(x) == 'string' then > return x:gmatch('.') > else > return ipairs(x) > end >end > >-- insert within string >function string.insert(str1, str2, pos) > return str1:sub(1,pos)..str2..str1:sub(pos+1) >end > >function string.remove(s, pos) > return s:sub(1,pos-1)..s:sub(pos+1) >end > >function string.pos(s, sub) > return string.find(s, sub, 1, true) -- plain=true to disable regular expressions >end > >-- TODO: backport utf-8 support from Lua 5.3 - __teliva_timestamp: original debugy: >debugy = 5 - __teliva_timestamp: original dbg: >-- helper for debug by print; overlay debug information towards the right >-- reset debugy every time you refresh screen >function dbg(window, s) > local oldy = 0 > local oldx = 0 > oldy, oldx = window:getyx() > window:mvaddstr(debugy, 60, s) > debugy = debugy+1 > window:mvaddstr(oldy, oldx, '') >end - __teliva_timestamp: original check: >function check(x, msg) > if x then > Window:addch('.') > else > print('F - '..msg) > print(' '..str(x)..' is false/nil') > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original check_eq: >function check_eq(x, expected, msg) > if eq(x, expected) then > Window:addch('.') > else > print('F - '..msg) > print(' expected '..str(expected)..' but got '..str(x)) > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original eq: >function eq(a, b) > if type(a) ~= type(b) then return false end > if type(a) == 'table' then > if #a ~= #b then return false end > for k, v in pairs(a) do > if b[k] ~= v then > return false > end > end > for k, v in pairs(b) do > if a[k] ~= v then > return false > end > end > return true > end > return a == b >end - __teliva_timestamp: original str: >-- smarter tostring >-- slow; used only for debugging >function str(x) > if type(x) == 'table' then > local result = '' > result = result..#x..'{' > for k, v in pairs(x) do > result = result..str(k)..'='..str(v)..', ' > end > result = result..'}' > return result > elseif type(x) == 'string' then > return '"'..x..'"' > end > return tostring(x) >end - __teliva_timestamp: original find_index: >function find_index(arr, x) > for n, y in ipairs(arr) do > if x == y then > return n > end > end >end - __teliva_timestamp: original trim: >function trim(s) > return s:gsub('^%s*', ''):gsub('%s*$', '') >end - __teliva_timestamp: original split: >function split(s, d) > result = {} > for match in (s..d):gmatch("(.-)"..d) do > table.insert(result, match); > end > return result >end - __teliva_timestamp: original map: >-- only for arrays >function map(l, f) > result = {} > for _, x in ipairs(l) do > table.insert(result, f(x)) > end > return result >end - __teliva_timestamp: original reduce: >-- only for arrays >function reduce(l, f, init) > result = init > for _, x in ipairs(l) do > result = f(result, x) > end > return result >end - __teliva_timestamp: original filter: >function filter(h, f) > result = {} > for k, v in pairs(h) do > if f(k, v) then > result[k] = v > end > end > return result >end - __teliva_timestamp: original ifilter: >-- only for arrays >function ifilter(l, f) > result = {} > for _, x in ipairs(l) do > if f(x) then > table.insert(result, x) > end > end > return result >end - __teliva_timestamp: original sort_letters: >function sort_letters(s) > tmp = {} > for i=1,#s do > table.insert(tmp, s[i]) > end > table.sort(tmp) > local result = '' > for _, c in pairs(tmp) do > result = result..c > end > return result >end > >function test_sort_letters(s) > check_eq(sort_letters(''), '', 'test_sort_letters: empty') > check_eq(sort_letters('ba'), 'ab', 'test_sort_letters: non-empty') > check_eq(sort_letters('abba'), 'aabb', 'test_sort_letters: duplicates') >end - __teliva_timestamp: original count_letters: >-- TODO: handle unicode >function count_letters(s) > local result = {} > for i=1,s:len() do > local c = s[i] > if result[c] == nil then > result[c] = 1 > else > result[c] = result[c] + 1 > end > end > return result >end - __teliva_timestamp: original count: >-- turn an array of elements into a map from elements to their frequency >-- analogous to count_letters for non-strings >function count(a) > local result = {} > for i, v in ipairs(a) do > if result[v] == nil then > result[v] = 1 > else > result[v] = result[v] + 1 > end > end > return result >end - __teliva_timestamp: original union: >function union(a, b) > for k, v in pairs(b) do > a[k] = v > end > return a >end - __teliva_timestamp: original subtract: >-- set subtraction >function subtract(a, b) > for k, v in pairs(b) do > a[k] = nil > end > return a >end - __teliva_timestamp: original all: >-- universal quantifier on sets >function all(s, f) > for k, v in pairs(s) do > if not f(k, v) then > return false > end > end > return true >end - __teliva_timestamp: original to_array: >-- turn a set into an array >-- drops values >function to_array(h) > local result = {} > for k, _ in pairs(h) do > table.insert(result, k) > end > return result >end - __teliva_timestamp: original append: >-- concatenate list 'elems' into 'l', modifying 'l' in the process >function append(l, elems) > for i=1,#elems do > table.insert(l, elems[i]) > end >end - __teliva_timestamp: original prepend: >-- concatenate list 'elems' into the start of 'l', modifying 'l' in the process >function prepend(l, elems) > for i=1,#elems do > table.insert(l, i, elems[i]) > end >end - __teliva_timestamp: original all_but: >function all_but(x, idx) > if type(x) == 'table' then > local result = {} > for i, elem in ipairs(x) do > if i ~= idx then > table.insert(result,elem) > end > end > return result > elseif type(x) == 'string' then > if idx < 1 then return x:sub(1) end > return x:sub(1, idx-1) .. x:sub(idx+1) > else > error('all_but: unsupported type '..type(x)) > end >end > >function test_all_but() > check_eq(all_but('', 0), '', 'all_but: empty') > check_eq(all_but('abc', 0), 'abc', 'all_but: invalid low index') > check_eq(all_but('abc', 4), 'abc', 'all_but: invalid high index') > check_eq(all_but('abc', 1), 'bc', 'all_but: first index') > check_eq(all_but('abc', 3), 'ab', 'all_but: final index') > check_eq(all_but('abc', 2), 'ac', 'all_but: middle index') >end - __teliva_timestamp: original set: >function set(l) > local result = {} > for i, elem in ipairs(l) do > result[elem] = true > end > return result >end - __teliva_timestamp: original set_eq: >function set_eq(l1, l2) > return eq(set(l1), set(l2)) >end > >function test_set_eq() > check(set_eq({1}, {1}), 'set_eq: identical') > check(not set_eq({1, 2}, {1, 3}), 'set_eq: different') > check(set_eq({1, 2}, {2, 1}), 'set_eq: order') > check(set_eq({1, 2, 2}, {2, 1}), 'set_eq: duplicates') >end - __teliva_timestamp: original clear: >function clear(lines) > while #lines > 0 do > table.remove(lines) > end >end - __teliva_timestamp: original zap: >function zap(target, src) > clear(target) > append(target, src) >end - __teliva_timestamp: original mfactorial: >-- memoized version of factorial >-- doesn't memoize recursive calls, but may be good enough >mfactorial = memo1(factorial) - __teliva_timestamp: original factorial: >function factorial(n) > local result = 1 > for i=1,n do > result = result*i > end > return result >end - __teliva_timestamp: original memo1: >-- a higher-order function that takes a function of a single arg >-- (that never returns nil) >-- and returns a memoized version of it >function memo1(f) > local memo = {} > return function(x) > if memo[x] == nil then > memo[x] = f(x) > end > return memo[x] > end >end > >-- mfactorial doesn't seem noticeably faster >function test_memo1() > for i=0,30 do > check_eq(mfactorial(i), factorial(i), 'memo1 over factorial: '..str(i)) > end >end - __teliva_timestamp: original num_permutations: >-- number of permutations of n distinct objects, taken r at a time >function num_permutations(n, r) > return factorial(n)/factorial(n-r) >end > >-- mfactorial doesn't seem noticeably faster >function test_memo1() > for i=0,30 do > for j=0,i do > check_eq(num_permutations(i, j), mfactorial(i)/mfactorial(i-j), 'num_permutations memoizes: '..str(i)..'P'..str(j)) > end > end >end - __teliva_timestamp: original menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = { > {'^k', 'clear'}, > {'^w', 'write prose to file "toot" (edit hotkey does NOT save)'}, >} - __teliva_timestamp: original Window: >Window = curses.stdscr() >curses.curs_set(0) -- we'll simulate our own cursor - __teliva_timestamp: original main: >function main() > init_colors() > > while true do > render(Window) > update(Window) > end >end - __teliva_timestamp: original init_colors: >function init_colors() > for i=0,7 do > curses.init_pair(i, i, -1) > end > curses.init_pair(8, 7, 0) > curses.init_pair(9, 7, 1) > curses.init_pair(10, 7, 2) > curses.init_pair(11, 7, 3) > curses.init_pair(12, 7, 4) > curses.init_pair(13, 7, 5) > curses.init_pair(14, 7, 6) > curses.init_pair(15, -1, 15) >end - __teliva_timestamp: original prose: >prose = '' - __teliva_timestamp: original cursor: >cursor = 1 - __teliva_timestamp: original render: >function render(window) > window:clear() > debugy = 5 > local toots = split(prose, '\n\n===\n\n') > pos = 1 > for i, toot in ipairs(toots) do > if i > 1 then > pos = render_delimiter(window, '\n\n===\n\n', pos, cursor) > end > pos = render_text(window, toot, pos, cursor) > print('') > window:attron(curses.A_BOLD) > window:addstr(toot:len()) > window:attroff(curses.A_BOLD) > end > window:refresh() >end - __teliva_timestamp: original render_delimiter: >function render_delimiter(window, s, pos, cursor) > local newpos = pos > for i=1,s:len() do > if newpos == cursor and i ~= 1 then > if s[i] == '\n' then > -- newline at cursor = render extra space in reverse video before jumping to new line > window:attron(curses.A_REVERSE) > window:addch(' ') > window:attroff(curses.A_REVERSE) > window:addstr(s[i]) > else > -- most characters at cursor = render in reverse video > window:attron(curses.A_REVERSE) > window:addstr(s[i]) > window:attroff(curses.A_REVERSE) > end > else > window:addstr(s[i]) > end > newpos = newpos+1 > end > return newpos >end - __teliva_timestamp: original render_text: >-- https://gankra.github.io/blah/text-hates-you >-- https://lord.io/text-editing-hates-you-too > >-- manual tests: >-- cursor on some character >-- cursor on (within) '\n\n===\n\n' delimiter (delimiter is hardcoded; things may break if you change it) >-- cursor at end of each line >-- render digits > >-- positions serve two purposes: >-- character to index into prose >-- cursor-printing > >-- sequence of stories >-- focus on rendering a single piece of text, first get that rock-solid >-- split prose into toots, manage transitions between toots in response to cursor movements >-- cursor movement: left/right vs up/down > >-- what is the ideal representation? >-- prose + cursor has issues in multi-toot context. when to display cursor? >function render_text(window, s, pos, cursor) > local newpos = pos >--? dbg(window, '--') > for i=1,s:len() do >--? dbg(window, tostring(newpos)..' '..tostring(string.byte(s[i]))) > if newpos == cursor then >--? dbg(window, 'cursor: '..tostring(cursor)) > if s[i] == '\n' then > -- newline at cursor = render extra space in reverse video before jumping to new line > window:attron(curses.A_REVERSE) > window:addch(' ') > window:attroff(curses.A_REVERSE) > window:addstr(s[i]) > else > -- most characters at cursor = render in reverse video > window:attron(curses.A_REVERSE) > window:addstr(s[i]) > window:attroff(curses.A_REVERSE) > end > else > window:addstr(s[i]) > end > newpos = newpos+1 > end > if newpos == cursor then > window:attron(curses.A_REVERSE) > window:addch(' ') > window:attroff(curses.A_REVERSE) > end > return newpos >end - __teliva_timestamp: original update: >function update(window) > local key = window:getch() > local h, w = window:getmaxyx() > if key == curses.KEY_LEFT then > if cursor > 1 then > cursor = cursor-1 > end > elseif key == curses.KEY_RIGHT then > if cursor <= #prose then > cursor = cursor+1 > end > elseif key == curses.KEY_DOWN then > cursor = cursor_down(prose, cursor, w) > elseif key == curses.KEY_UP then > cursor = cursor_up(prose, cursor, w) > elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then -- ctrl-h, ctrl-?, delete > if cursor > 1 then > cursor = cursor-1 > prose = prose:remove(cursor) > end > elseif key == 11 then -- ctrl-k > prose = '' > cursor = 1 > elseif key == 23 then -- ctrl-w > local out = io.open('toot', 'w') > if out ~= nil then > out:write(prose, '\n') > out:close() > end > elseif key == 10 or (key >= 32 and key < 127) then > prose = prose:insert(string.char(key), cursor-1) > cursor = cursor+1 > end >end - __teliva_timestamp: original cursor_down: >function cursor_down(s, old_idx, width) > local max = string.len(s) > local i = 1 > -- compute oldcol, the screen column of old_idx > local oldcol = 0 > local col = 0 > while true do > if i > max then > -- abnormal old_idx > return old_idx > end > if i == old_idx then > oldcol = col > break > end > if s[i] == '\n' then > col = 0 > else > col = col+1 > end > i = i+1 > end > -- skip rest of line > while true do > if i > max then > -- current line is at bottom > if col >= width then > return i > end > return old_idx > end > if s[i] == '\n' then > break > end > if i - old_idx >= width then > return i > end > col = col+1 > i = i+1 > end > -- compute index at same column on next line > -- i is at a newline > i = i+1 > col = 0 > while true do > if i > max then > -- next line is at bottom and is too short; position at end of it > return i > end > if s[i] == '\n' then > -- next line is too short; position at end of it > return i > end > if col == oldcol then > return i > end > col = col+1 > i = i+1 > end >end > >function test_cursor_down() > -- lines that don't wrap > check_eq(cursor_down('abc\ndef', 1, 5), 5, 'cursor_down: non-bottom line first char') > check_eq(cursor_down('abc\ndef', 2, 5), 6, 'cursor_down: non-bottom line mid char') > check_eq(cursor_down('abc\ndef', 3, 5), 7, 'cursor_down: non-bottom line final char') > check_eq(cursor_down('abc\ndef', 4, 5), 8, 'cursor_down: non-bottom line end') > check_eq(cursor_down('abc\ndef', 5, 5), 5, 'cursor_down: bottom line first char') > check_eq(cursor_down('abc\ndef', 6, 5), 6, 'cursor_down: bottom line mid char') > check_eq(cursor_down('abc\ndef', 7, 5), 7, 'cursor_down: bottom line final char') > check_eq(cursor_down('abc\n\ndef', 2, 5), 5, 'cursor_down: to shorter line') > > -- within a single wrapping line > -- |abcde| <-- wrap, no newline > -- |fgh | > check_eq(cursor_down('abcdefgh', 1, 5), 6, 'cursor_down from wrapping line: first char') > check_eq(cursor_down('abcdefgh', 2, 5), 7, 'cursor_down from wrapping line: mid char') > check_eq(cursor_down('abcdefgh', 5, 5), 9, 'cursor_down from wrapping line: to shorter line') > > -- within a single very long wrapping line > -- |abcde| <-- wrap, no newline > -- |fghij| <-- wrap, no newline > -- |klm | > check_eq(cursor_down('abcdefghijklm', 1, 5), 6, 'cursor_down within wrapping line: first char') > check_eq(cursor_down('abcdefghijklm', 2, 5), 7, 'cursor_down within wrapping line: mid char') > check_eq(cursor_down('abcdefghijklm', 5, 5), 10, 'cursor_down within wrapping line: final char') >end - __teliva_timestamp: original cursor_up: >function cursor_up(s, old_idx, width) > local max = string.len(s) > local i = 1 > -- compute oldcol, the screen column of old_idx > local oldcol = 0 > local col = 0 > local newline_before_current_line = 0 > while true do > if i > max or i == old_idx then > oldcol = col > break > end > if s[i] == '\n' then > col = 0 > newline_before_current_line = i > else > col = col+1 > if col == width then > col = 0 > end > end > i = i+1 > end > -- find previous newline > i = i-col-1 > if old_idx - newline_before_current_line > width then > -- we're in a wrapped line > return old_idx - width > end > -- scan back to start of previous line > if s[i] == '\n' then > i = i-1 > end > while true do > if i < 1 then > -- current line is at top > break > end > if s[i] == '\n' then > break > end > i = i-1 > end > -- i is at a newline > i = i+1 > -- skip whole screen lines within previous line > while newline_before_current_line - i > width do > i = i + width > end > -- compute index at same column on previous screen line > col = 0 > while true do > if i > max then > -- next line is at bottom and is too short; position at end of it > return i > end > if s[i] == '\n' then > -- next line is too short; position at end of it > return i > end > if col == oldcol then > return i > end > col = col+1 > i = i+1 > end >end > >function test_cursor_up() > -- lines that don't wrap > check_eq(cursor_up('abc\ndef', 1, 5), 1, 'cursor_up: top line first char') > check_eq(cursor_up('abc\ndef', 2, 5), 2, 'cursor_up: top line mid char') > check_eq(cursor_up('abc\ndef', 3, 5), 3, 'cursor_up: top line final char') > check_eq(cursor_up('abc\ndef', 4, 5), 4, 'cursor_up: top line end') > check_eq(cursor_up('abc\ndef', 5, 5), 1, 'cursor_up: non-top line first char') > check_eq(cursor_up('abc\ndef', 6, 5), 2, 'cursor_up: non-top line mid char') > check_eq(cursor_up('abc\ndef', 7, 5), 3, 'cursor_up: non-top line final char') > check_eq(cursor_up('abc\ndef\n', 8, 5), 4, 'cursor_up: non-top line end') > check_eq(cursor_up('ab\ndef\n', 7, 5), 3, 'cursor_up: to shorter line') > > -- within a single wrapping line > -- |abcde| <-- wrap, no newline > -- |fgh | > check_eq(cursor_up('abcdefgh', 6, 5), 1, 'cursor_up from wrapping line: first char') > check_eq(cursor_up('abcdefgh', 7, 5), 2, 'cursor_up from wrapping line: mid char') > check_eq(cursor_up('abcdefgh', 8, 5), 3, 'cursor_up from wrapping line: final char') > check_eq(cursor_up('abcdefgh', 9, 5), 4, 'cursor_up from wrapping line: wrapped line end') > > -- within a single very long wrapping line > -- |abcde| <-- wrap, no newline > -- |fghij| <-- wrap, no newline > -- |klm | > check_eq(cursor_up('abcdefghijklm', 11, 5), 6, 'cursor_up within wrapping line: first char') > check_eq(cursor_up('abcdefghijklm', 12, 5), 7, 'cursor_up within wrapping line: mid char') > check_eq(cursor_up('abcdefghijklm', 13, 5), 8, 'cursor_up within wrapping line: final char') > check_eq(cursor_up('abcdefghijklm', 14, 5), 9, 'cursor_up within wrapping line: wrapped line end') > > -- from below to (the bottom of) a wrapping line > -- |abcde| <-- wrap, no newline > -- |fg | > -- |hij | > check_eq(cursor_up('abcdefg\nhij', 9, 5), 6, 'cursor_up to wrapping line: first char') > check_eq(cursor_up('abcdefg\nhij', 10, 5), 7, 'cursor_up to wrapping line: mid char') > check_eq(cursor_up('abcdefg\nhij', 11, 5), 8, 'cursor_up to wrapping line: final char') > check_eq(cursor_up('abcdefg\nhij', 12, 5), 8, 'cursor_up to wrapping line: to shorter line') >end - __teliva_timestamp: >Thu Feb 17 19:52:30 2022 doc:blurb: >A tiny editor (no scrolling) for composing a series of toots or tweets. Always shows character counts for current state of prose. > >Typing '===' on its own lines, surrounded by empty lines, partitions prose and gives all segments independent character counts. Good for threads (tweetstorms). - __teliva_timestamp: >Fri Mar 11 09:45:27 2022 first_toot: >first_toot = 1 - __teliva_timestamp: >Fri Mar 11 11:47:34 2022 update: >function update(window) > local key = window:getch() > local h, w = window:getmaxyx() > if key == curses.KEY_LEFT then > if cursor > 1 then > cursor = cursor-1 > end > elseif key == curses.KEY_RIGHT then > if cursor <= #prose then > cursor = cursor+1 > end > elseif key == curses.KEY_DOWN then > cursor = cursor_down(prose, cursor, w) > elseif key == curses.KEY_UP then > cursor = cursor_up(prose, cursor, w) > elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then -- ctrl-h, ctrl-?, delete > if cursor > 1 then > cursor = cursor-1 > prose = prose:remove(cursor) > end > elseif key == 6 then -- ctrl-f > first_toot = first_toot+1 > elseif key == 2 then -- ctrl-b > if first_toot > 1 then > first_toot = first_toot-1 > end > elseif key == 11 then -- ctrl-k > prose = '' > cursor = 1 > elseif key == 23 then -- ctrl-w > local out = io.open('toot', 'w') > if out ~= nil then > out:write(prose, '\n') > out:close() > end > elseif key == 10 or (key >= 32 and key < 127) then > prose = prose:insert(string.char(key), cursor-1) > cursor = cursor+1 > end >end - __teliva_timestamp: >Fri Mar 11 11:48:43 2022 menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = { > {'^w', 'write to "toot"'}, > {'^f|^b', 'scroll'}, > {'^k', 'clear'}, >} - __teliva_timestamp: >Sat Mar 12 08:48:44 2022 render: >function render(window) > window:clear() > debugy = 5 > local toots = split(prose, '\n\n===\n\n') > pos = 1 > for i, toot in ipairs(toots) do >--? dbg(window, "render: "..i.." pos "..pos.." cursor "..cursor) > if i > 1 then > pos = render_delimiter(window, '\n\n===\n\n', pos, cursor) >--? dbg(window, "delim: "..pos.." cursor "..cursor) > end > if i <= first_toot then > window:clear() > end > pos = render_text(window, toot, pos, cursor) > print('') >--? dbg(window, "text: "..pos.." cursor "..cursor) > window:attron(curses.A_BOLD) > window:addstr(toot:len()) > window:attroff(curses.A_BOLD) > end > window:refresh() >end - __teliva_timestamp: >Sat Mar 12 08:57:41 2022 doc:blurb: >A tiny editor (no scrolling) for composing a series of toots or tweets. >Always shows character counts for current state of prose. > >Typing '===' on its own lines, surrounded by empty lines, partitions prose and gives all segments independent character counts. Good for threads (tweetstorms). - __teliva_timestamp: >Sat Mar 12 08:59:52 2022 __teliva_note: >hacky scrolling support > >Since I started out rendering a toot at a time and tracking the position >as I rendered each toot, the easiest way to build this was to scroll a >toot at a time, always render each toot and just decide when to stop >clearing the screen. This way I don't mess with the position computation >logic which is carefully synced between render and cursor_up/cursor_down. > >But there may be a more elegant approach if I was building the current state >from scratch. doc:blurb: >A tiny editor for composing a short series of toots or tweets. Always shows character counts for current state of prose. > >Typing '===' on its own lines, surrounded by empty lines, partitions prose and gives all segments independent character counts. Good for threads (tweetstorms). > >Scrolling support is rudimentary. Keys to scroll are independent of cursor movement, so cursor can move off the screen and confusingly 'get lost'. - __teliva_timestamp: >Wed Mar 30 21:33:17 2022 update: >function update(window) > local key = window:getch() > local h, w = window:getmaxyx() > if key == curses.KEY_LEFT then > if cursor > 1 then > cursor = cursor-1 > end > elseif key == curses.KEY_RIGHT then > if cursor <= #prose then > cursor = cursor+1 > end > elseif key == curses.KEY_DOWN then > cursor = cursor_down(prose, cursor, w) > elseif key == curses.KEY_UP then > cursor = cursor_up(prose, cursor, w) > elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then -- ctrl-h, ctrl-?, delete > if cursor > 1 then > cursor = cursor-1 > prose = prose:remove(cursor) > end > elseif key == 6 then -- ctrl-f > first_toot = first_toot+1 > elseif key == 2 then -- ctrl-b > if first_toot > 1 then > first_toot = first_toot-1 > end > elseif key == 23 then -- ctrl-w > local out = io.open('toot', 'w') > if out ~= nil then > out:write(prose, '\n') > out:close() > end > elseif key == 10 or (key >= 32 and key < 127) then > prose = prose:insert(string.char(key), cursor-1) > cursor = cursor+1 > end >end - __teliva_timestamp: >Wed Mar 30 21:33:44 2022 __teliva_note: >Get rid of the ctrl-k shortcut. Makes it too easy to lose data. To clear the page just quit and restart. menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = { > {'^w', 'write to "toot"'}, > {'^f|^b', 'scroll'}, >} - __teliva_timestamp: >Thu Mar 31 08:42:19 2022 update: >function update(window) > local key = window:getch() > local h, w = window:getmaxyx() > if key == curses.KEY_LEFT then > if cursor > 1 then > cursor = cursor-1 > end > elseif key == curses.KEY_RIGHT then > if cursor <= #prose then > cursor = cursor+1 > end > elseif key == curses.KEY_DOWN then > cursor = cursor_down(prose, cursor, w) > elseif key == curses.KEY_UP then > cursor = cursor_up(prose, cursor, w) > elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then -- ctrl-h, ctrl-?, delete > if cursor > 1 then > cursor = cursor-1 > prose = prose:remove(cursor) > end > elseif key == 6 then -- ctrl-f > first_toot = first_toot+1 > elseif key == 2 then -- ctrl-b > if first_toot > 1 then > first_toot = first_toot-1 > end > elseif key == 23 then -- ctrl-w > local out = io.open(next_toot(), 'w') > if out ~= nil then > out:write(prose, '\n') > out:close() > end > elseif key == 10 or (key >= 32 and key < 127) then > prose = prose:insert(string.char(key), cursor-1) > cursor = cursor+1 > end >end - __teliva_timestamp: >Thu Mar 31 08:44:19 2022 next_toot: >-- pick the first filename starting with 'toot' that doesn't exist yet >function next_toot() > if not file_exists('toot') then return 'toot' end > local idx = 1 > while true do > local curr = 'toot'..str(idx) > if not file_exists(curr) then > return curr > end > idx = idx+1 > end >end - __teliva_timestamp: >Thu Mar 31 08:46:27 2022 file_exists: >function file_exists(filename) > local f = io.open(filename, 'r') > return f ~= nil >end ================================================ FILE: tour.md ================================================ # A guided tour of Teliva Teliva is an environment for running shareable little text-mode Lua apps that are easy to modify. This page is an in-progress guided tour through [Teliva's Readme](https://github.com/akkartik/teliva#readme) and Lua's reference documentation. We'll start out really slow and gradually accelerate. _Prerequisites_ You will need the following to build Teliva: * A non-mobile computer running some sort of Unix variant. Teliva has been tested on Linux, OpenBSD and Mac OS X. (Other BSD variants and Windows Subsystem for Linux should require minor changes at most. Please [contact me](http://akkartik.name/contact) if you run into issues running Teliva on your computer.) * [Git](https://git-scm.com). * A working C toolchain. * Some fluency in typing commands at the terminal and interpreting their output. If you have trouble with any of this, [I'm always nearby and available to answer questions](http://akkartik.name/contact). The prerequisites are just things I haven't figured out how to explain yet. In particular, I want this page to be accessible to people who are in the process of learning programming, but I'm sure it isn't good enough yet for that. Ask me questions and help me improve it. ## Task 1: running a Teliva app Read the first question in [the Readme](https://github.com/akkartik/teliva/blob/main/README.md). Try running the commands to download and build Teliva. If you have any trouble at this point, don't waste _any_ time thinking about it. Just [get in touch](http://akkartik.name/contact). Run the simplest example app: ```sh src/teliva counter.tlv ``` screenshot of Teliva running a counter app Can you figure out what this app does, what you can do with it? There's a number on screen. Hit `enter`. The number increments by 1. Hit `ctrl-x` to exit (press `ctrl` and `x` on your keyboard at the same time). Run the app again. Try editing the app by hitting `ctrl-e`. You see a "big picture" view of the app. Spend a few moments scanning this page. editing the code for the counter app All programs consist of data and _functions_: code that operates on data in some way. Teliva apps always start by running the special function `main`. The big picture orders functions from the _top down_. It shows `main` up top, functions that `main` calls below, functions called by _those_ functions next, and so on. Try browsing to some of the names visible on screen. Don't be afraid to experiment. The menu at the bottom always shows the hotkeys available to you at any point in time. Don't worry, everything you do can be undone. ## Task 2: modifying a Teliva app The [first section of the Lua book](https://www.lua.org/pil/1.html) starts with this one-line example: ```lua print("Hello World") ``` Can you figure out where to add this line to the app's code so it's visible when the app runs? Start by looking inside `main`. Most Teliva apps tend to share a basic structure in `main`: - some initialization, followed by - a loop that repeatedly: * updates the screen, and then * waits for the user to press a key Can you map the lines of `main` to this structure? Which function describes how the app updates the screen? Some hints: - The function responsible for updating the screen is `render`. - `render` begins by clearing the screen (`window` in Teliva). After you make a change, can you figure out how to run your program? Does it do what you expect? Feel free to edit your programs as often as necessary. Programming is just long sessions of repeatedly editing (`ctrl-e`) and running (`ctrl-e`) your program. Once you're happy with your change, try exiting Teliva and restarting it with the same app. Do you still see your changes? You could save the file `counter.tlv` anywhere you like at this point. Or share it with someone else. Everything needed for the app is in that file. If you get stuck or have a question, [send it to me!](http://akkartik.name/contact) ## Task 3: variables and arithmetic Can you figure out how to modify this app to increment by 2 each time you hit `enter`? Again, don't be afraid to experiment. The menu at the bottom always shows the hotkeys available to you at any point in time. Don't worry, everything you do can be undone. Divide the problem into two parts in your head: where to make your change and what change to make there. You've already gotten some practice selecting a place to modify in Task 2. Repeat that process. Go back to the `render` page. What is the name of the _variable_ (box containing a number) that decides what number gets printed to screen? Go back to the big picture. Where is this variable defined? How does it get modified when you press `enter`? If you're stuck, some short sections from [the Lua book](https://www.lua.org/pil/contents.html) might help at this point: [getting started](https://www.lua.org/pil/1.html); [assignment](https://www.lua.org/pil/4.1.html); [what you can do with numbers](https://www.lua.org/pil/3.1.html). (Buy the Lua book to support the creators of Lua. Teliva is a tiny molehill on the mountain of awesome that is Lua.) Some hints: - The function responsible for processing keystrokes is `update`. - The variable that tracks what number to print on screen is `n`. ================================================ FILE: zet.tlv ================================================ # .tlv file generated by https://github.com/akkartik/teliva # You may edit it if you are careful; however, you may see cryptic errors if you # violate Teliva's assumptions. # # .tlv files are representations of Teliva programs. Teliva programs consist of # sequences of definitions. Each definition is a table of key/value pairs. Keys # and values are both strings. # # Lines in .tlv files always follow exactly one of the following forms: # - comment lines at the top of the file starting with '#' at column 0 # - beginnings of definitions starting with '- ' at column 0, followed by a # key/value pair # - key/value pairs consisting of ' ' at column 0, containing either a # spaceless value on the same line, or a multi-line value # - multiline values indented by more than 2 spaces, starting with a '>' # # If these constraints are violated, Teliva may unceremoniously crash. Please # report bugs at http://akkartik.name/contact - __teliva_timestamp: original str_helpers: >-- some string helpers from http://lua-users.org/wiki/StringIndexing > >-- index characters using [] >getmetatable('').__index = function(str,i) > if type(i) == 'number' then > return str:sub(i,i) > else > return string[i] > end >end > >-- ranges using (), selected bytes using {} >getmetatable('').__call = function(str,i,j) > if type(i)~='table' then > return str:sub(i,j) > else > local t={} > for k,v in ipairs(i) do > t[k]=str:sub(v,v) > end > return table.concat(t) > end >end > >-- iterate over an ordered sequence >function q(x) > if type(x) == 'string' then > return x:gmatch('.') > else > return ipairs(x) > end >end > >-- insert within string >function string.insert(str1, str2, pos) > return str1:sub(1,pos)..str2..str1:sub(pos+1) >end > >function string.remove(s, pos) > return s:sub(1,pos-1)..s:sub(pos+1) >end > >function string.pos(s, sub) > return string.find(s, sub, 1, true) -- plain=true to disable regular expressions >end > >-- TODO: backport utf-8 support from Lua 5.3 - __teliva_timestamp: original debugy: >debugy = 5 - __teliva_timestamp: original dbg: >-- helper for debug by print; overlay debug information towards the right >-- reset debugy every time you refresh screen >function dbg(window, s) > local oldy = 0 > local oldx = 0 > oldy, oldx = window:getyx() > window:mvaddstr(debugy, 60, s) > debugy = debugy+1 > window:mvaddstr(oldy, oldx, '') >end - __teliva_timestamp: original check: >function check(x, msg) > if x then > Window:addch('.') > else > print('F - '..msg) > print(' '..str(x)..' is false/nil') > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original check_eq: >function check_eq(x, expected, msg) > if eq(x, expected) then > Window:addch('.') > else > print('F - '..msg) > print(' expected '..str(expected)..' but got '..str(x)) > teliva_num_test_failures = teliva_num_test_failures + 1 > -- overlay first test failure on editors > if teliva_first_failure == nil then > teliva_first_failure = msg > end > end >end - __teliva_timestamp: original eq: >function eq(a, b) > if type(a) ~= type(b) then return false end > if type(a) == 'table' then > if #a ~= #b then return false end > for k, v in pairs(a) do > if b[k] ~= v then > return false > end > end > for k, v in pairs(b) do > if a[k] ~= v then > return false > end > end > return true > end > return a == b >end - __teliva_timestamp: original str: >-- smarter tostring >-- slow; used only for debugging >function str(x) > if type(x) == 'table' then > local result = '' > result = result..#x..'{' > for k, v in pairs(x) do > result = result..str(k)..'='..str(v)..', ' > end > result = result..'}' > return result > elseif type(x) == 'string' then > return '"'..x..'"' > end > return tostring(x) >end - __teliva_timestamp: original find_index: >function find_index(arr, x) > for n, y in ipairs(arr) do > if x == y then > return n > end > end >end - __teliva_timestamp: original trim: >function trim(s) > return s:gsub('^%s*', ''):gsub('%s*$', '') >end - __teliva_timestamp: original split: >function split(s, d) > result = {} > for match in (s..d):gmatch("(.-)"..d) do > table.insert(result, match); > end > return result >end - __teliva_timestamp: original map: >-- only for arrays >function map(l, f) > result = {} > for _, x in ipairs(l) do > table.insert(result, f(x)) > end > return result >end - __teliva_timestamp: original reduce: >-- only for arrays >function reduce(l, f, init) > result = init > for _, x in ipairs(l) do > result = f(result, x) > end > return result >end - __teliva_timestamp: original filter: >function filter(h, f) > result = {} > for k, v in pairs(h) do > if f(k, v) then > result[k] = v > end > end > return result >end - __teliva_timestamp: original ifilter: >-- only for arrays >function ifilter(l, f) > result = {} > for _, x in ipairs(l) do > if f(x) then > table.insert(result, x) > end > end > return result >end - __teliva_timestamp: original sort_letters: >function sort_letters(s) > tmp = {} > for i=1,#s do > table.insert(tmp, s[i]) > end > table.sort(tmp) > local result = '' > for _, c in pairs(tmp) do > result = result..c > end > return result >end > >function test_sort_letters(s) > check_eq(sort_letters(''), '', 'test_sort_letters: empty') > check_eq(sort_letters('ba'), 'ab', 'test_sort_letters: non-empty') > check_eq(sort_letters('abba'), 'aabb', 'test_sort_letters: duplicates') >end - __teliva_timestamp: original count_letters: >-- TODO: handle unicode >function count_letters(s) > local result = {} > for i=1,s:len() do > local c = s[i] > if result[c] == nil then > result[c] = 1 > else > result[c] = result[c] + 1 > end > end > return result >end - __teliva_timestamp: original count: >-- turn an array of elements into a map from elements to their frequency >-- analogous to count_letters for non-strings >function count(a) > local result = {} > for i, v in ipairs(a) do > if result[v] == nil then > result[v] = 1 > else > result[v] = result[v] + 1 > end > end > return result >end - __teliva_timestamp: original union: >function union(a, b) > for k, v in pairs(b) do > a[k] = v > end > return a >end - __teliva_timestamp: original subtract: >-- set subtraction >function subtract(a, b) > for k, v in pairs(b) do > a[k] = nil > end > return a >end - __teliva_timestamp: original all: >-- universal quantifier on sets >function all(s, f) > for k, v in pairs(s) do > if not f(k, v) then > return false > end > end > return true >end - __teliva_timestamp: original to_array: >-- turn a set into an array >-- drops values >function to_array(h) > local result = {} > for k, _ in pairs(h) do > table.insert(result, k) > end > return result >end - __teliva_timestamp: original append: >-- concatenate list 'elems' into 'l', modifying 'l' in the process >function append(l, elems) > for i=1,#elems do > table.insert(l, elems[i]) > end >end - __teliva_timestamp: original prepend: >-- concatenate list 'elems' into the start of 'l', modifying 'l' in the process >function prepend(l, elems) > for i=1,#elems do > table.insert(l, i, elems[i]) > end >end - __teliva_timestamp: original all_but: >function all_but(x, idx) > if type(x) == 'table' then > local result = {} > for i, elem in ipairs(x) do > if i ~= idx then > table.insert(result,elem) > end > end > return result > elseif type(x) == 'string' then > if idx < 1 then return x:sub(1) end > return x:sub(1, idx-1) .. x:sub(idx+1) > else > error('all_but: unsupported type '..type(x)) > end >end > >function test_all_but() > check_eq(all_but('', 0), '', 'all_but: empty') > check_eq(all_but('abc', 0), 'abc', 'all_but: invalid low index') > check_eq(all_but('abc', 4), 'abc', 'all_but: invalid high index') > check_eq(all_but('abc', 1), 'bc', 'all_but: first index') > check_eq(all_but('abc', 3), 'ab', 'all_but: final index') > check_eq(all_but('abc', 2), 'ac', 'all_but: middle index') >end - __teliva_timestamp: original set: >function set(l) > local result = {} > for i, elem in ipairs(l) do > result[elem] = true > end > return result >end - __teliva_timestamp: original set_eq: >function set_eq(l1, l2) > return eq(set(l1), set(l2)) >end > >function test_set_eq() > check(set_eq({1}, {1}), 'set_eq: identical') > check(not set_eq({1, 2}, {1, 3}), 'set_eq: different') > check(set_eq({1, 2}, {2, 1}), 'set_eq: order') > check(set_eq({1, 2, 2}, {2, 1}), 'set_eq: duplicates') >end - __teliva_timestamp: original clear: >function clear(lines) > while #lines > 0 do > table.remove(lines) > end >end - __teliva_timestamp: original zap: >function zap(target, src) > clear(target) > append(target, src) >end - __teliva_timestamp: original mfactorial: >-- memoized version of factorial >-- doesn't memoize recursive calls, but may be good enough >mfactorial = memo1(factorial) - __teliva_timestamp: original factorial: >function factorial(n) > local result = 1 > for i=1,n do > result = result*i > end > return result >end - __teliva_timestamp: original memo1: >-- a higher-order function that takes a function of a single arg >-- (that never returns nil) >-- and returns a memoized version of it >function memo1(f) > local memo = {} > return function(x) > if memo[x] == nil then > memo[x] = f(x) > end > return memo[x] > end >end > >-- mfactorial doesn't seem noticeably faster >function test_memo1() > for i=0,30 do > check_eq(mfactorial(i), factorial(i), 'memo1 over factorial: '..str(i)) > end >end - __teliva_timestamp: original num_permutations: >-- number of permutations of n distinct objects, taken r at a time >function num_permutations(n, r) > return factorial(n)/factorial(n-r) >end > >-- mfactorial doesn't seem noticeably faster >function test_memo1() > for i=0,30 do > for j=0,i do > check_eq(num_permutations(i, j), mfactorial(i)/mfactorial(i-j), 'num_permutations memoizes: '..str(i)..'P'..str(j)) > end > end >end - __teliva_timestamp: original menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = { > {'^e', 'edit'}, >} - __teliva_timestamp: original Window: >Window = curses.stdscr() - __teliva_timestamp: original window: >-- constructor for fake screen and window >-- call it like this: >-- local w = window{ >-- kbd=kbd('abc'), >-- scr=scr{h=5, w=4}, >-- } >-- eventually it'll do everything a real ncurses window can >function window(h) > h.__index = h > setmetatable(h, h) > h.__index = function(table, key) > return rawget(h, key) > end > h.attrset = function(self, x) > self.scr.attrs = x > end > h.attron = function(self, x) > -- currently same as attrset since Lua 5.1 doesn't have bitwise operators > -- doesn't support multiple attrs at once >-- local old = self.scr.attrs >-- self.scr.attrs = old|x > self.scr.attrs = x > end > h.attroff = function(self, x) > -- currently borked since Lua 5.1 doesn't have bitwise operators > -- doesn't support multiple attrs at once >-- local old = self.scr.attrs >-- self.scr.attrs = old & (~x) > self.scr.attrs = curses.A_NORMAL > end > h.getch = function(self) > local c = table.remove(h.kbd, 1) > if c == nil then return c end > return string.byte(c) -- for verisimilitude with ncurses > end > h.addch = function(self, c) > local scr = self.scr > if c == '\n' then > scr.cursy = scr.cursy+1 > scr.cursx = 0 > return > end > if scr.cursy <= scr.h then > scr[scr.cursy][scr.cursx] = {data=c, attrs=scr.attrs} > scr.cursx = scr.cursx+1 > if scr.cursx > scr.w then > scr.cursy = scr.cursy+1 > scr.cursx = 1 > end > end > end > h.addstr = function(self, s) > for i=1,s:len() do > self:addch(s[i]) > end > end > h.mvaddch = function(self, y, x, c) > self.scr.cursy = y > self.scr.cursx = x > self:addch(c) > end > h.mvaddstr = function(self, y, x, s) > self.scr.cursy = y > self.scr.cursx = x > self:addstr(s) > end > h.clear = function(self) > clear_scr(self.scr) > end > h.refresh = function(self) > -- nothing > end > return h >end - __teliva_timestamp: original kbd: >function kbd(keys) > local result = {} > for i=1,keys:len() do > table.insert(result, keys[i]) > end > return result >end - __teliva_timestamp: original scr: >function scr(props) > props.cursx = 1 > props.cursy = 1 > clear_scr(props) > return props >end - __teliva_timestamp: original clear_scr: >function clear_scr(props) > props.cursy = 1 > props.cursx = 1 > for y=1,props.h do > props[y] = {} > for x=1,props.w do > props[y][x] = {data=' ', attrs=curses.A_NORMAL} > end > end > return props >end - __teliva_timestamp: original check_screen: >function check_screen(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > check_eq(window.scr[y][x].data, contents[i], message..'/'..y..','..x) > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end > >-- putting it all together, an example test of both keyboard and screen >function test_check_screen() > local lines = { > c='123', > d='234', > a='345', > b='456', > } > local w = window{ > kbd=kbd('abc'), > scr=scr{h=3, w=5}, > } > local y = 1 > while true do > local b = w:getch() > if b == nil then break end > w:mvaddstr(y, 1, lines[string.char(b)]) > y = y+1 > end > check_screen(w, '345 '.. > '456 '.. > '123 ', > 'test_check_screen') >end - __teliva_timestamp: original check_reverse: >function check_reverse(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > if contents[i] ~= ' ' then > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & curses.A_REVERSE, message..'/'..y..','..x) > check_eq(window.scr[y][x].attrs, curses.A_REVERSE, message..'/'..y..','..x) > else > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & (~curses.A_REVERSE), message..'/'..y..','..x) > check(window.scr[y][x].attrs ~= curses.A_REVERSE, message..'/'..y..','..x) > end > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end - __teliva_timestamp: original check_bold: >function check_bold(window, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > if contents[i] ~= ' ' then > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & curses.A_BOLD, message..'/'..y..','..x) > check_eq(window.scr[y][x].attrs, curses.A_BOLD, message..'/'..y..','..x) > else > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & (~curses.A_BOLD), message..'/'..y..','..x) > check(window.scr[y][x].attrs ~= curses.A_BOLD, message..'/'..y..','..x) > end > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end - __teliva_timestamp: original check_color: >-- check which parts of a screen have the given color_pair >function check_color(window, cp, contents, message) > local x, y = 1, 1 > for i=1,contents:len() do > if contents[i] ~= ' ' then > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & curses.color_pair(cp), message..'/'..y..','..x) > check_eq(window.scr[y][x].attrs, curses.color_pair(cp), message..'/'..y..','..x) > else > -- hacky version while we're without bitwise operators on Lua 5.1 >-- check(window.scr[y][x].attrs & (~curses.A_BOLD), message..'/'..y..','..x) > check(window.scr[y][x].attrs ~= curses.color_pair(cp), message..'/'..y..','..x) > end > x = x+1 > if x > window.scr.w then > y = y+1 > x = 1 > end > end >end - __teliva_timestamp: original spaces: >function spaces(n) > for i=1,n do > Window:addch(' ') > end >end - __teliva_timestamp: original init_colors: >function init_colors() > -- light background > curses.init_pair(view_settings.current_zettel_bg, 236, 230) > curses.init_pair(1, 236, 250) > curses.init_pair(2, 236, 252) > -- dark background >--? curses.init_pair(view_settings.current_zettel_bg, 252, 130) >--? curses.init_pair(1, 252, 240) >--? curses.init_pair(2, 252, 242) >end - __teliva_timestamp: original main: >function main() > init_colors() > current_zettel_id = zettels.root > > while true do > render(Window) > update(Window) > end >end - __teliva_timestamp: original depth: >function depth(zettel) > local result = 0 > while zettel.parent do > result = result+1 > zettel = zettel.parent > end > return result >end - __teliva_timestamp: original render_zettel: >function render_zettel(window, bg, indent, starty, startx, zettel) > window:attrset(curses.color_pair(bg)) > for y=0,view_settings.height-1 do > for x=0,view_settings.width-1 do > window:mvaddch(y+starty, x+startx, ' ') > end > end > local y, x = 0, indent+1 > for i=1,#zettel.data do > local c = zettel.data[i] > if c == '\n' then > y = y+1 > x = indent+1 > else > window:mvaddstr(y+starty, x+startx, c) > x = x+1 > if x >= startx + view_settings.width then > y = y+1 > x = indent+1 > end > end > if y >= view_settings.height then > break > end > end >end - __teliva_timestamp: original current_zettel_id: >current_zettel_id = '' - __teliva_timestamp: original view_settings: >view_settings = { > -- dimensions for rendering a single zettel; extra text gets truncated > width=50, > height=3, > -- spacing between zettels > hmargin=1, > vmargin=1, > -- > indent=2, -- how children of a zettel are indicated > current_zettel_bg=3, -- color pair index initialized in init_colors >} - __teliva_timestamp: original zettels: >zettels = { > root="a", > a={ > data="abc\ndef", > child="c", > next="b", > }, > b={ > data="ghi\njklm", > prev="a", > }, > c={ > data="c", > parent="a", > next="d", > }, > d={ > data="d", > parent="a", > prev="c", > } >} - __teliva_timestamp: original render_state: >-- some information about what's been drawn on screen >render_state = { > -- where the current zettel is, in units of zettels > curr_h = 1, > curr_w = 1, > -- what zettel is at each position on screen, in units of zettels > hw2id = {}, >} - __teliva_timestamp: original update: >function update(window) > local key = window:getch() > local curr = zettels[current_zettel_id] > -- graph-based navigation > if key == string.byte('j') then > if curr.child then > current_zettel_id = curr.child > elseif curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > elseif key == string.byte('k') then > if curr.parent then current_zettel_id = curr.parent end > elseif key == string.byte('h') then > if curr.prev then > current_zettel_id = curr.prev > elseif curr.parent then > current_zettel_id = curr.parent > end > elseif key == string.byte('l') then > if curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > -- screen-based navigation > elseif key == curses.KEY_UP then > if render_state.curr_h > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h - 1] > end > elseif key == curses.KEY_DOWN then > if render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] > end > elseif key == curses.KEY_LEFT then > if render_state.curr_w > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w - 1][render_state.curr_h] > end > elseif key == curses.KEY_RIGHT then > if render_state.wh2id[render_state.curr_w + 1] and render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] then > current_zettel_id = render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] > end > -- > elseif key == 5 then -- ctrl-e > editz(window) > end >end - __teliva_timestamp: original render: >function render(window) > window:clear() > local lines, cols = window:getmaxyx() > local bg=1 > local y, x = 0, 0 -- units of characters (0-based) > local w, h = 1, 1 -- units of zettels (1-based) > -- render zettels depth-first, while tracking relative positions > local done = {} > local inprogress = {zettels.root} > render_state.wh2id = {{}} > while #inprogress > 0 do > local currid = table.remove(inprogress) > if not done[currid] then > done[currid] = true > table.insert(render_state.wh2id[w], currid) > local zettel = zettels[currid] > if currid == current_zettel_id then > render_state.curr_w = w > render_state.curr_h = h > end > local currbg = (currid == current_zettel_id) and view_settings.current_zettel_bg or bg > render_zettel(window, currbg, depth(zettel) * view_settings.indent, y, x, zettel) > if zettel.next then table.insert(inprogress, zettel.next) end > if zettel.child then table.insert(inprogress, zettel.child) end > bg = 3 - bg -- toggle between color pairs 1 and 2 > y = y + view_settings.height + view_settings.vmargin > h = h + 1 > if y + view_settings.height > lines then > y = 0 > h = 1 > x = x + view_settings.width + view_settings.hmargin > w = w + 1 > if x + view_settings.width > cols then break end > table.insert(render_state.wh2id, {}) > end > end > end > window:mvaddstr(lines-2, 0, '') > for i=1,3 do > window:attrset(curses.color_pair(i%2+1)) > window:addstr('') > spaces(view_settings.width-string.len('')) > window:attrset(curses.color_pair(0)) > window:addstr(' ') -- margin > end > window:mvaddstr(lines-1, 0, '? ') > window:refresh() >end - __teliva_timestamp: original view_settings: >view_settings = { > -- dimensions for rendering a single zettel; extra text gets truncated > width=50, > height=3, > -- spacing between zettels > hmargin=1, > vmargin=1, > -- > indent=2, -- how children of a zettel are indicated > current_zettel_bg=3, -- color pair index initialized in init_colors >} - __teliva_timestamp: original editz: >function editz(window) > menu = { {'^e', 'back to browsing'},} > local top = (render_state.curr_h - 1) * (view_settings.height + view_settings.vmargin) > local bottom = top + view_settings.height > local left = (render_state.curr_w - 1) * (view_settings.width + view_settings.hmargin) > local right = left + view_settings.width > local cursor = 1 > curses.curs_set(0) > local quit = false > while not quit do > editz_render(window, zettels[current_zettel_id].data, cursor, top, bottom, left, right) > quit, zettels[current_zettel_id].data, cursor = editz_update(window, zettels[current_zettel_id].data, cursor) > end > curses.curs_set(1) >end - __teliva_timestamp: original editz_render: >function editz_render(window, s, cursor, top, minbottom, left, right) > local h, w = window:getmaxyx() > window:attrset(curses.color_pair(view_settings.current_zettel_bg)) > for y=top,minbottom-1 do > for x=left,right-1 do > window:mvaddch(y, x, ' ') > end > end > local y, x = top, left + 1 -- left padding; TODO: indent > window:mvaddstr(y, x, '') > for i=1,s:len() do > -- render character > if i == cursor then > if s[i] == '\n' then > -- newline at cursor = render extra space in reverse video before jumping to new line > window:attron(curses.A_REVERSE) > window:addch(' ') > window:attroff(curses.A_REVERSE) > else > -- most characters at cursor = render in reverse video > window:attron(curses.A_REVERSE) > window:addstr(s[i]) > window:attroff(curses.A_REVERSE) > end > else > if s[i] ~= '\n' then > window:addstr(s[i]) > end > end > -- update cursor position > if s[i] == '\n' then > if i == cursor then x = x + 1; end > for col=x,right-1 do window:addch(' '); end > x = left > y = y + 1 > if y >= h-2 then return end > window:mvaddstr(y, x, '') > for col=x,right-1 do window:addch(' '); end > x = left + 1 -- left padding; TODO: indent > window:mvaddstr(y, x, '') > else > x = x + 1 > if x >= right then > y = y + 1 > if y >= h-2 then return end > x = left + 1 -- left padding; TODO: indent > window:mvaddstr(y, x, '') > end > end > end > if cursor > s:len() then > window:attron(curses.A_REVERSE) > window:addch(' ') > window:attroff(curses.A_REVERSE) > else > window:addch(' ') > end >end - __teliva_timestamp: original editz_update: >function editz_update(window, prose, cursor) > local key = window:getch() > local h, w = window:getmaxyx() > if key == curses.KEY_LEFT then > if cursor > 1 then > cursor = cursor-1 > end > elseif key == curses.KEY_RIGHT then > if cursor <= #prose then > cursor = cursor+1 > end > elseif key == curses.KEY_DOWN then > cursor = cursor_down(prose, cursor, w) > elseif key == curses.KEY_UP then > cursor = cursor_up(prose, cursor, w) > elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then -- ctrl-h, ctrl-?, delete > if cursor > 1 then > cursor = cursor-1 > prose = prose:remove(cursor) > end > elseif key == 5 then -- ctrl-e > return true, prose, cursor > elseif key == 10 or (key >= 32 and key < 127) then > prose = prose:insert(string.char(key), cursor-1) > cursor = cursor+1 > end > return false, prose, cursor >end - __teliva_timestamp: original cursor_down: >function cursor_down(s, old_idx, width) > local max = s:len() > local i = 1 > -- compute oldcol, the screen column of old_idx > local oldcol = 0 > local col = 0 > while true do > if i > max then > -- abnormal old_idx > return old_idx > end > if i == old_idx then > oldcol = col > break > end > if s[i] == '\n' then > col = 0 > else > col = col+1 > end > i = i+1 > end > -- skip rest of line > while true do > if i > max then > -- current line is at bottom > if col >= width then > return i > end > return old_idx > end > if s[i] == '\n' then > break > end > if i - old_idx >= width then > return i > end > col = col+1 > i = i+1 > end > -- compute index at same column on next line > -- i is at a newline > i = i+1 > col = 0 > while true do > if i > max then > -- next line is at bottom and is too short; position at end of it > return i > end > if s[i] == '\n' then > -- next line is too short; position at end of it > return i > end > if col == oldcol then > return i > end > col = col+1 > i = i+1 > end >end > >function test_cursor_down() > -- lines that don't wrap > check_eq(cursor_down('abc\ndef', 1, 5), 5, 'cursor_down: non-bottom line first char') > check_eq(cursor_down('abc\ndef', 2, 5), 6, 'cursor_down: non-bottom line mid char') > check_eq(cursor_down('abc\ndef', 3, 5), 7, 'cursor_down: non-bottom line final char') > check_eq(cursor_down('abc\ndef', 4, 5), 8, 'cursor_down: non-bottom line end') > check_eq(cursor_down('abc\ndef', 5, 5), 5, 'cursor_down: bottom line first char') > check_eq(cursor_down('abc\ndef', 6, 5), 6, 'cursor_down: bottom line mid char') > check_eq(cursor_down('abc\ndef', 7, 5), 7, 'cursor_down: bottom line final char') > check_eq(cursor_down('abc\n\ndef', 2, 5), 5, 'cursor_down: to shorter line') > > -- within a single wrapping line > -- |abcde| <-- wrap, no newline > -- |fgh | > check_eq(cursor_down('abcdefgh', 1, 5), 6, 'cursor_down from wrapping line: first char') > check_eq(cursor_down('abcdefgh', 2, 5), 7, 'cursor_down from wrapping line: mid char') > check_eq(cursor_down('abcdefgh', 5, 5), 9, 'cursor_down from wrapping line: to shorter line') > > -- within a single very long wrapping line > -- |abcde| <-- wrap, no newline > -- |fghij| <-- wrap, no newline > -- |klm | > check_eq(cursor_down('abcdefghijklm', 1, 5), 6, 'cursor_down within wrapping line: first char') > check_eq(cursor_down('abcdefghijklm', 2, 5), 7, 'cursor_down within wrapping line: mid char') > check_eq(cursor_down('abcdefghijklm', 5, 5), 10, 'cursor_down within wrapping line: final char') >end - __teliva_timestamp: original __teliva_note: >initial commit: show/edit zettels cursor_up: >function cursor_up(s, old_idx, width) > local max = s:len() > local i = 1 > -- compute oldcol, the screen column of old_idx > local oldcol = 0 > local col = 0 > local newline_before_current_line = 0 > while true do > if i > max or i == old_idx then > oldcol = col > break > end > if s[i] == '\n' then > col = 0 > newline_before_current_line = i > else > col = col+1 > if col == width then > col = 0 > end > end > i = i+1 > end > -- find previous newline > i = i-col-1 > if old_idx - newline_before_current_line > width then > -- we're in a wrapped line > return old_idx - width > end > -- scan back to start of previous line > if s[i] == '\n' then > i = i-1 > end > while true do > if i < 1 then > -- current line is at top > break > end > if s[i] == '\n' then > break > end > i = i-1 > end > -- i is at a newline > i = i+1 > -- skip whole screen lines within previous line > while newline_before_current_line - i > width do > i = i + width > end > -- compute index at same column on previous screen line > col = 0 > while true do > if i > max then > -- next line is at bottom and is too short; position at end of it > return i > end > if s[i] == '\n' then > -- next line is too short; position at end of it > return i > end > if col == oldcol then > return i > end > col = col+1 > i = i+1 > end >end > >function test_cursor_up() > -- lines that don't wrap > check_eq(cursor_up('abc\ndef', 1, 5), 1, 'cursor_up: top line first char') > check_eq(cursor_up('abc\ndef', 2, 5), 2, 'cursor_up: top line mid char') > check_eq(cursor_up('abc\ndef', 3, 5), 3, 'cursor_up: top line final char') > check_eq(cursor_up('abc\ndef', 4, 5), 4, 'cursor_up: top line end') > check_eq(cursor_up('abc\ndef', 5, 5), 1, 'cursor_up: non-top line first char') > check_eq(cursor_up('abc\ndef', 6, 5), 2, 'cursor_up: non-top line mid char') > check_eq(cursor_up('abc\ndef', 7, 5), 3, 'cursor_up: non-top line final char') > check_eq(cursor_up('abc\ndef\n', 8, 5), 4, 'cursor_up: non-top line end') > check_eq(cursor_up('ab\ndef\n', 7, 5), 3, 'cursor_up: to shorter line') > > -- within a single wrapping line > -- |abcde| <-- wrap, no newline > -- |fgh | > check_eq(cursor_up('abcdefgh', 6, 5), 1, 'cursor_up from wrapping line: first char') > check_eq(cursor_up('abcdefgh', 7, 5), 2, 'cursor_up from wrapping line: mid char') > check_eq(cursor_up('abcdefgh', 8, 5), 3, 'cursor_up from wrapping line: final char') > check_eq(cursor_up('abcdefgh', 9, 5), 4, 'cursor_up from wrapping line: wrapped line end') > > -- within a single very long wrapping line > -- |abcde| <-- wrap, no newline > -- |fghij| <-- wrap, no newline > -- |klm | > check_eq(cursor_up('abcdefghijklm', 11, 5), 6, 'cursor_up within wrapping line: first char') > check_eq(cursor_up('abcdefghijklm', 12, 5), 7, 'cursor_up within wrapping line: mid char') > check_eq(cursor_up('abcdefghijklm', 13, 5), 8, 'cursor_up within wrapping line: final char') > check_eq(cursor_up('abcdefghijklm', 14, 5), 9, 'cursor_up within wrapping line: wrapped line end') > > -- from below to (the bottom of) a wrapping line > -- |abcde| <-- wrap, no newline > -- |fg | > -- |hij | > check_eq(cursor_up('abcdefg\nhij', 9, 5), 6, 'cursor_up to wrapping line: first char') > check_eq(cursor_up('abcdefg\nhij', 10, 5), 7, 'cursor_up to wrapping line: mid char') > check_eq(cursor_up('abcdefg\nhij', 11, 5), 8, 'cursor_up to wrapping line: final char') > check_eq(cursor_up('abcdefg\nhij', 12, 5), 8, 'cursor_up to wrapping line: to shorter line') >end - __teliva_timestamp: >Wed Feb 9 08:15:25 2022 render: >function render(window) > window:clear() > local lines, cols = window:getmaxyx() > local bg=1 > local y, x = 0, 0 -- units of characters (0-based) > local w, h = 1, 1 -- units of zettels (1-based) > -- render zettels depth-first, while tracking relative positions > local done = {} > local inprogress = {zettels.root} > render_state.wh2id = {{}} > while #inprogress > 0 do > local currid = table.remove(inprogress) > if not done[currid] then > done[currid] = true > table.insert(render_state.wh2id[w], currid) > local zettel = zettels[currid] > if currid == current_zettel_id then > render_state.curr_w = w > render_state.curr_h = h > end > local currbg = (currid == current_zettel_id) and view_settings.current_zettel_bg or bg > render_zettel(window, currbg, depth(zettel) * view_settings.indent, y, x, zettel) > if zettel.next then table.insert(inprogress, zettel.next) end > if zettel.child then table.insert(inprogress, zettel.child) end > bg = 3 - bg -- toggle between color pairs 1 and 2 > y = y + view_settings.height + view_settings.vmargin > h = h + 1 > if y + view_settings.height > lines then > y = 0 > h = 1 > x = x + view_settings.width + view_settings.hmargin > w = w + 1 > if x + view_settings.width > cols then break end > table.insert(render_state.wh2id, {}) > end > end > end > window:mvaddstr(lines-1, 0, '') > for i=1,3 do > window:attrset(curses.color_pair(i%2+1)) > window:addstr('') > spaces(view_settings.width-string.len('')) > window:attrset(curses.color_pair(0)) > window:addstr(' ') -- margin > end > window:refresh() >end - __teliva_timestamp: >Wed Feb 9 08:15:35 2022 main: >function main() > init_colors() > current_zettel_id = zettels.root > > curses.curs_set(0) > while true do > render(Window) > update(Window) > end >end - __teliva_timestamp: >Wed Feb 9 08:16:24 2022 __teliva_note: >get rid of commandline > >There's a reason vim hides it. Confusing to have two cursors on screen. editz: >function editz(window) > menu = { {'^e', 'back to browsing'},} > local top = (render_state.curr_h - 1) * (view_settings.height + view_settings.vmargin) > local bottom = top + view_settings.height > local left = (render_state.curr_w - 1) * (view_settings.width + view_settings.hmargin) > local right = left + view_settings.width > local cursor = 1 > local quit = false > while not quit do > editz_render(window, zettels[current_zettel_id].data, cursor, top, bottom, left, right) > quit, zettels[current_zettel_id].data, cursor = editz_update(window, zettels[current_zettel_id].data, cursor) > end >end - __teliva_timestamp: >Wed Feb 9 08:22:20 2022 editz_render: >function editz_render(window, s, cursor, top, minbottom, left, right) > local h, w = window:getmaxyx() > local cursor_y, cursor_x = 0, 0 > window:attrset(curses.color_pair(view_settings.current_zettel_bg)) > for y=top,minbottom-1 do > for x=left,right-1 do > window:mvaddch(y, x, ' ') > end > end > local y, x = top, left + 1 -- left padding; TODO: indent > window:mvaddstr(y, x, '') > for i=1,s:len() do > if i == cursor then > cursor_y = y > cursor_x = x > end > if s[i] ~= '\n' then > window:addstr(s[i]) > x = x + 1 > if x >= right then > y = y + 1 > if y >= h-2 then return end > x = left + 1 -- left padding; TODO: indent > window:mvaddstr(y, x, '') > end > else > for col=x+1,right-1 do window:addch(' '); end > x = left > y = y + 1 > if y >= h-2 then return end > window:mvaddstr(y, x, '') > for col=x,right-1 do window:addch(' '); end > x = left + 1 -- left padding; TODO: indent > window:mvaddstr(y, x, '') > end > end > if cursor_y == 0 and cursor_x == 0 then > cursor_y = y > cursor_x = x > end > window:mvaddstr(cursor_y, cursor_x, '') >end - __teliva_timestamp: >Wed Feb 9 08:25:05 2022 editz: >function editz(window) > local old_menu = menu > menu = { {'^e', 'back to browsing'},} > local top = (render_state.curr_h - 1) * (view_settings.height + view_settings.vmargin) > local bottom = top + view_settings.height > local left = (render_state.curr_w - 1) * (view_settings.width + view_settings.hmargin) > local right = left + view_settings.width > local cursor = zettels[current_zettel_id].data:len()+1 > local quit = false > curses.curs_set(1) > while not quit do > editz_render(window, zettels[current_zettel_id].data, cursor, top, bottom, left, right) > quit, zettels[current_zettel_id].data, cursor = editz_update(window, zettels[current_zettel_id].data, cursor) > end > curses.curs_set(0) > menu = old_menu >end - __teliva_timestamp: >Wed Feb 9 08:28:13 2022 __teliva_note: >stop simulating the cursor > >editz_render is now much simpler editz_update: >function editz_update(window, prose, cursor) > local key = window:getch() > local h, w = window:getmaxyx() > if key == curses.KEY_LEFT then > if cursor > 1 then > cursor = cursor-1 > end > elseif key == curses.KEY_RIGHT then > if cursor <= #prose then > cursor = cursor+1 > end > elseif key == curses.KEY_DOWN then > cursor = cursor_down(prose, cursor, w) > elseif key == curses.KEY_UP then > cursor = cursor_up(prose, cursor, w) > elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then -- ctrl-h, ctrl-?, delete > if cursor > 1 then > cursor = cursor-1 > prose = prose:remove(cursor) > end > elseif key == 5 then -- ctrl-e > return true, prose, cursor > elseif key == 10 or (key >= 32 and key < 127) then > prose = prose:insert(string.char(key), cursor-1) > cursor = cursor+1 > end > return false, prose, cursor >end - __teliva_timestamp: >Wed Feb 9 17:55:52 2022 menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = { > {'j', 'child'}, > {'k', 'parent'}, > {'l,h', 'next/prev sib'}, > {'e', 'edit'}, >} - __teliva_timestamp: >Wed Feb 9 17:56:18 2022 __teliva_note: >no need for chords once we drop the commandline update: >function update(window) > local key = window:getch() > local curr = zettels[current_zettel_id] > -- graph-based navigation > if key == string.byte('j') then > if curr.child then > current_zettel_id = curr.child > elseif curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > elseif key == string.byte('k') then > if curr.parent then current_zettel_id = curr.parent end > elseif key == string.byte('h') then > if curr.prev then > current_zettel_id = curr.prev > elseif curr.parent then > current_zettel_id = curr.parent > end > elseif key == string.byte('l') then > if curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > -- screen-based navigation > elseif key == curses.KEY_UP then > if render_state.curr_h > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h - 1] > end > elseif key == curses.KEY_DOWN then > if render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] > end > elseif key == curses.KEY_LEFT then > if render_state.curr_w > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w - 1][render_state.curr_h] > end > elseif key == curses.KEY_RIGHT then > if render_state.wh2id[render_state.curr_w + 1] and render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] then > current_zettel_id = render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] > end > -- > elseif key == string.byte('e') then > local old_menu = menu > editz(window) > menu = old_menu > end >end - __teliva_timestamp: >Wed Feb 9 18:00:42 2022 menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = { > {'j', 'child'}, > {'k', 'parent'}, > {'l,h', 'next/prev sib'}, > {'e', 'edit'}, > {'a,b', 'insert sib'}, > {'c', 'insert child'}, >} - __teliva_timestamp: >Wed Feb 9 18:16:23 2022 zettels: >zettels = { > root="id1", > final=4, > id1={ > data="this is zettel A\n\nit has some text", > child="id3", > next="id2", > }, > id2={ > data="this is a sibling of zettel A at the top level", > prev="id1", > }, > id3={ > data="this is zettel B, a child of A", > parent="id1", > next="id4", > }, > id4={ > data="this is another child of zettel A, a sibling of B", > parent="id1", > prev="id3", > } >} - __teliva_timestamp: >Wed Feb 9 23:04:49 2022 new_id: >function new_id() > zettels.final = zettels.final+1 > local result = 'id'..tostring(zettels.final) > zettels[result] = {} > return result >end - __teliva_timestamp: >Wed Feb 9 23:10:57 2022 __teliva_note: >creating new zettels > >feels natural to immediately start editing them update: >function update(window) > local key = window:getch() > local curr = zettels[current_zettel_id] > assert(curr, string.format('cursor fell off the edge of the world: %s', type(current_zettel_id))) > -- graph-based navigation > if key == string.byte('j') then > if curr.child then > current_zettel_id = curr.child > elseif curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > elseif key == string.byte('k') then > if curr.parent then current_zettel_id = curr.parent end > elseif key == string.byte('h') then > if curr.prev then > current_zettel_id = curr.prev > elseif curr.parent then > current_zettel_id = curr.parent > end > elseif key == string.byte('l') then > if curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > -- screen-based navigation > elseif key == curses.KEY_UP then > if render_state.curr_h > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h - 1] > end > elseif key == curses.KEY_DOWN then > if render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] > end > elseif key == curses.KEY_LEFT then > if render_state.curr_w > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w - 1][render_state.curr_h] > end > elseif key == curses.KEY_RIGHT then > if render_state.wh2id[render_state.curr_w + 1] and render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] then > current_zettel_id = render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] > end > -- > elseif key == string.byte('e') then > editz(window) > elseif key == string.byte('a') then > -- insert sibling after > local old = curr.next > curr.next = new_id() > local new = zettels[curr.next] > new.data = '' > new.next = old > zettels[old].prev = curr.next > new.prev = current_zettel_id > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > new.parent = curr.parent > current_zettel_id = curr.next > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('b') then > -- insert sibling before > local old = curr.prev > curr.prev = new_id() > local new = zettels[curr.prev] > new.data = '' > new.prev = old > zettels[old].next = curr.prev > new.next = current_zettel_id > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > new.parent = curr.parent > current_zettel_id = curr.prev > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('c') then > -- insert child > local old = curr.child > curr.child = new_id() > local new = zettels[curr.child] > new.data = '' > new.next = old > assert(zettels[old].prev == nil, "first child shouldn't have a previous sibling") > zettels[old].prev = curr.child > new.parent = curr > current_zettel_id = curr.child > render(window) -- recompute render_state > editz(window) > end >end - __teliva_timestamp: >Thu Feb 10 00:01:58 2022 update: >function update(window) > local key = window:getch() > local h, w = window:getmaxyx() > local curr = zettels[current_zettel_id] > assert(curr, string.format('cursor fell off the edge of the world: %s', type(current_zettel_id))) > -- graph-based navigation > if key == string.byte('j') then > if curr.child then > current_zettel_id = curr.child > elseif curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > elseif key == string.byte('k') then > if curr.parent then current_zettel_id = curr.parent end > elseif key == string.byte('h') then > if curr.prev then > current_zettel_id = curr.prev > elseif curr.parent then > current_zettel_id = curr.parent > end > elseif key == string.byte('l') then > if curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > -- screen-based navigation > elseif key == curses.KEY_UP then > if render_state.curr_h > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h - 1] > end > elseif key == curses.KEY_DOWN then > if render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] > end > elseif key == curses.KEY_LEFT then > if render_state.curr_w > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w - 1][render_state.curr_h] > end > elseif key == curses.KEY_RIGHT then > if render_state.wh2id[render_state.curr_w + 1] and render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] then > current_zettel_id = render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] > end > -- > elseif key == string.byte('e') then > editz(window) > elseif key == string.byte('a') then > -- insert sibling after > local old = curr.next > curr.next = new_id() > local new = zettels[curr.next] > new.data = '' > new.next = old > zettels[old].prev = curr.next > new.prev = current_zettel_id > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > new.parent = curr.parent > current_zettel_id = curr.next > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('b') then > -- insert sibling before > local old = curr.prev > curr.prev = new_id() > local new = zettels[curr.prev] > new.data = '' > new.prev = old > zettels[old].next = curr.prev > new.next = current_zettel_id > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > new.parent = curr.parent > current_zettel_id = curr.prev > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('c') then > -- insert child > local old = curr.child > curr.child = new_id() > local new = zettels[curr.child] > new.data = '' > new.next = old > assert(zettels[old].prev == nil, "first child shouldn't have a previous sibling") > zettels[old].prev = curr.child > new.parent = curr > current_zettel_id = curr.child > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('x') then > if view_settings.width > 5 then > view_settings.width = view_settings.width - 5 > end > elseif key == string.byte('X') then > if view_settings.width < w-5 then > view_settings.width = view_settings.width + 5 > end > elseif key == string.byte('y') then > if view_settings.height > 0 then > view_settings.height = view_settings.height - 1 > end > elseif key == string.byte('Y') then > if view_settings.height < h-2 then > view_settings.height = view_settings.height + 1 > end > end >end - __teliva_timestamp: >Thu Feb 10 00:02:35 2022 menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = { > {'j', 'child'}, > {'k', 'parent'}, > {'l,h', 'next/prev sib'}, > {'e', 'edit'}, > {'a,b', 'insert sib'}, > {'c', 'insert child'}, > {'x,X,y,Y', 'resize'}, >} - __teliva_timestamp: >Thu Feb 10 06:57:51 2022 __teliva_note: >squeeze menu to make way for next feature menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = { > {'a,b,c', 'insert sib/child'}, > {'e', 'edit'}, > {'j,k,l,h', 'move to child/parent/sib'}, > {'x,X,y,Y', 'resize'}, >} - __teliva_timestamp: >Thu Feb 10 07:00:46 2022 __teliva_note: >bugfix: handle missing parent/child/sib update: >function update(window) > local key = window:getch() > local h, w = window:getmaxyx() > local curr = zettels[current_zettel_id] > assert(curr, string.format('cursor fell off the edge of the world: %s', type(current_zettel_id))) > -- graph-based navigation > if key == string.byte('j') then > if curr.child then > current_zettel_id = curr.child > elseif curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > elseif key == string.byte('k') then > if curr.parent then current_zettel_id = curr.parent end > elseif key == string.byte('h') then > if curr.prev then > current_zettel_id = curr.prev > elseif curr.parent then > current_zettel_id = curr.parent > end > elseif key == string.byte('l') then > if curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > -- screen-based navigation > elseif key == curses.KEY_UP then > if render_state.curr_h > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h - 1] > end > elseif key == curses.KEY_DOWN then > if render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] > end > elseif key == curses.KEY_LEFT then > if render_state.curr_w > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w - 1][render_state.curr_h] > end > elseif key == curses.KEY_RIGHT then > if render_state.wh2id[render_state.curr_w + 1] and render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] then > current_zettel_id = render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] > end > -- > elseif key == string.byte('e') then > editz(window) > elseif key == string.byte('a') then > -- insert sibling after > local old = curr.next > curr.next = new_id() > local new = zettels[curr.next] > new.data = '' > new.next = old > new.prev = current_zettel_id > if old then > zettels[old].prev = curr.next > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > end > new.parent = curr.parent > current_zettel_id = curr.next > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('b') then > -- insert sibling before > local old = curr.prev > curr.prev = new_id() > local new = zettels[curr.prev] > new.data = '' > new.prev = old > new.next = current_zettel_id > if old then > zettels[old].next = curr.prev > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > end > new.parent = curr.parent > current_zettel_id = curr.prev > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('c') then > -- insert child > local old = curr.child > curr.child = new_id() > local new = zettels[curr.child] > new.data = '' > new.next = old > if old then > assert(zettels[old].prev == nil, "first child shouldn't have a previous sibling") > zettels[old].prev = curr.child > end > new.parent = curr > current_zettel_id = curr.child > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('x') then > if view_settings.width > 5 then > view_settings.width = view_settings.width - 5 > end > elseif key == string.byte('X') then > if view_settings.width < w-5 then > view_settings.width = view_settings.width + 5 > end > elseif key == string.byte('y') then > if view_settings.height > 0 then > view_settings.height = view_settings.height - 1 > end > elseif key == string.byte('Y') then > if view_settings.height < h-2 then > view_settings.height = view_settings.height + 1 > end > end >end - __teliva_timestamp: >Thu Feb 10 07:27:43 2022 write_zettels: >function write_zettels(outfile) > outfile:write(json.encode(zettels)) > outfile:close() >end - __teliva_timestamp: >Thu Feb 10 07:28:30 2022 read_zettels: >function read_zettels(infile) > zettels = json.decode(infile:read('*a')) > infile:close() >end - __teliva_timestamp: >Thu Feb 10 07:30:25 2022 __teliva_note: >saving/loading zettels to/from disk main: >function main() > init_colors() > curses.curs_set(0) -- hide cursor except when editing > > -- read zettels from disk if possible > local infile = io.open('zet', 'r') > if infile then > read_zettels(infile) > else > local outfile = io.open('zet', 'w') > if outfile then > write_zettels(outfile) > end > end > current_zettel_id = zettels.root > > while true do > render(Window) > update(Window) > > -- save zettels, but hold on to previous state on disk > -- until last possible second > local filename = os.tmpname() > local outfile = io.open(filename, 'w') > write_zettels(outfile) > os.rename(filename, 'zet') > end >end - __teliva_timestamp: >Thu Feb 10 07:32:46 2022 __teliva_note: >stop writing sample zettels to disk > >That was just for ease of testing write_zettels() main: >function main() > init_colors() > curses.curs_set(0) -- hide cursor except when editing > > local infile = io.open('zet', 'r') > if infile then > read_zettels(infile) > end > current_zettel_id = zettels.root > > while true do > render(Window) > update(Window) > > -- save zettels, but hold on to previous state on disk > -- until last possible second > local filename = os.tmpname() > local outfile = io.open(filename, 'w') > if outfile then > write_zettels(outfile) > os.rename(filename, 'zet') > end > end >end - __teliva_timestamp: >Thu Feb 10 07:43:39 2022 zettels: >-- initial state of the zettels >-- if you came here to clear the zettels, >-- delete everything (ctrl-k and ctrl-u will delete a whole line at a time) >-- until it looks like this: >-- >-- zettels = { >-- root='id1', >-- final=1, >-- id1={ >-- data='', >-- }, >-- } >-- >-- I don't yet trust any deletion feature I create to not mess up your data. >-- Besides, this is a good excuse to start making this app your own. > >zettels = { > root='id1', > final=5, > id1={ > data='this is zettel A\n\nit has some text', > child='id3', > next='id2', > }, > id2={ > data='this is a sibling of zettel A at the top level', > prev='id1', > next='id5', > }, > id3={ > data='this is zettel B, a child of A', > parent='id1', > next='id4', > }, > id4={ > data='this is another child of zettel A, a sibling of B', > parent='id1', > prev='id3', > }, > id5={ > data="(To clean up these sample zettels, hit ctrl-u and edit 'zettels')\n\nI don't yet trust any deletion feature I create to not mess up your data.\nBesides, this is a good excuse to start making this app your own.)", > prev='id2', > }, >} - __teliva_timestamp: >Thu Feb 10 20:24:13 2022 menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = { > {'a,b,c', 'insert'}, > {'e', 'edit'}, > {'j,k,l,h', 'move'}, > {'x,X,y,Y', 'resize'}, > {'s', 'stash'}, > {'t', 'link with stash'}, >} - __teliva_timestamp: >Thu Feb 10 20:25:14 2022 stash: >stash = nil - __teliva_timestamp: >Thu Feb 10 20:32:38 2022 update: >function update(window) > local key = window:getch() > local h, w = window:getmaxyx() > local curr = zettels[current_zettel_id] > assert(curr, string.format('cursor fell off the edge of the world: %s', type(current_zettel_id))) > -- move along the graph > if key == string.byte('j') then > if curr.child then > current_zettel_id = curr.child > elseif curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > elseif key == string.byte('k') then > if curr.parent then current_zettel_id = curr.parent end > elseif key == string.byte('h') then > if curr.prev then > current_zettel_id = curr.prev > elseif curr.parent then > current_zettel_id = curr.parent > end > elseif key == string.byte('l') then > if curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > -- move along the screen > elseif key == curses.KEY_UP then > if render_state.curr_h > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h - 1] > end > elseif key == curses.KEY_DOWN then > if render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] > end > elseif key == curses.KEY_LEFT then > if render_state.curr_w > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w - 1][render_state.curr_h] > end > elseif key == curses.KEY_RIGHT then > if render_state.wh2id[render_state.curr_w + 1] and render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] then > current_zettel_id = render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] > end > -- mutations > elseif key == string.byte('e') then > editz(window) > elseif key == string.byte('a') then > -- insert sibling after > local old = curr.next > curr.next = new_id() > local new = zettels[curr.next] > new.data = '' > new.next = old > new.prev = current_zettel_id > if old then > zettels[old].prev = curr.next > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > end > new.parent = curr.parent > current_zettel_id = curr.next > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('b') then > -- insert sibling before > local old = curr.prev > curr.prev = new_id() > local new = zettels[curr.prev] > new.data = '' > new.prev = old > new.next = current_zettel_id > if old then > zettels[old].next = curr.prev > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > end > new.parent = curr.parent > current_zettel_id = curr.prev > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('c') then > -- insert child > local old = curr.child > curr.child = new_id() > local new = zettels[curr.child] > new.data = '' > new.next = old > if old then > assert(zettels[old].prev == nil, "first child shouldn't have a previous sibling") > zettels[old].prev = curr.child > end > new.parent = curr > current_zettel_id = curr.child > render(window) -- recompute render_state > editz(window) > -- cross-links > elseif key == string.byte('s') then > -- save zettel to a stash > stash = current_zettel_id > elseif key == string.byte('t') then > -- cross-link a zettel bidirectionally with what's on the stash > if curr.crosslinks then > curr.crosslinks.a = stash > else > curr.crosslinks = {a=stash} > end > -- view settings > elseif key == string.byte('x') then > if view_settings.width > 5 then > view_settings.width = view_settings.width - 5 > end > elseif key == string.byte('X') then > if view_settings.width < w-5 then > view_settings.width = view_settings.width + 5 > end > elseif key == string.byte('y') then > if view_settings.height > 0 then > view_settings.height = view_settings.height - 1 > end > elseif key == string.byte('Y') then > if view_settings.height < h-2 then > view_settings.height = view_settings.height + 1 > end > end >end - __teliva_timestamp: >Thu Feb 10 20:39:15 2022 render: >function render(window) > window:clear() > local lines, cols = window:getmaxyx() > local bg=1 > local y, x = 0, 0 -- units of characters (0-based) > local w, h = 1, 1 -- units of zettels (1-based) > -- render zettels depth-first, while tracking relative positions > local done = {} > local inprogress = {zettels.root} > render_state.wh2id = {{}} > while #inprogress > 0 do > local currid = table.remove(inprogress) > if not done[currid] then > done[currid] = true > table.insert(render_state.wh2id[w], currid) > local zettel = zettels[currid] > if currid == current_zettel_id then > render_state.curr_w = w > render_state.curr_h = h > end > local currbg = (currid == current_zettel_id) and view_settings.current_zettel_bg or bg > render_zettel(window, currbg, depth(zettel) * view_settings.indent, y, x, zettel) > if zettel.next then table.insert(inprogress, zettel.next) end > if zettel.child then table.insert(inprogress, zettel.child) end > if zettel.crosslinks then > for relation, target in pairs(zettel.crosslinks) do > table.insert(inprogress, target) > end > end > bg = 3 - bg -- toggle between color pairs 1 and 2 > y = y + view_settings.height + view_settings.vmargin > h = h + 1 > if y + view_settings.height > lines then > y = 0 > h = 1 > x = x + view_settings.width + view_settings.hmargin > w = w + 1 > if x + view_settings.width > cols then break end > table.insert(render_state.wh2id, {}) > end > end > end > window:mvaddstr(lines-1, 0, '') > bg = 1 > x = 0 > for i=1,3 do > local zettel = nil > if i == 1 and stash then > zettel = zettels[stash] > end > render_zettel(window, bg, 0, lines-1, x, zettel) > bg = 3 - bg > x = x + view_settings.width + view_settings.hmargin > end > window:refresh() >end - __teliva_timestamp: >Thu Feb 10 20:40:08 2022 __teliva_note: >initial support for cross-links > >Kinda confusing because zettels still show indent based on their >hierarchical location rather than the path they're rendered in. render_zettel: >function render_zettel(window, bg, indent, starty, startx, zettel) > window:attrset(curses.color_pair(bg)) > for y=0,view_settings.height-1 do > for x=0,view_settings.width-1 do > window:mvaddch(y+starty, x+startx, ' ') > end > end > local y, x = 0, indent+1 > local data = '' > if zettel then > data = zettel.data > end > for i=1,#data do > local c = data[i] > if c == '\n' then > y = y+1 > x = indent+1 > else > window:mvaddstr(y+starty, x+startx, c) > x = x+1 > if x >= startx + view_settings.width then > y = y+1 > x = indent+1 > end > end > if y >= view_settings.height then > break > end > end >end - __teliva_timestamp: >Thu Feb 10 20:44:29 2022 __teliva_note: >looks better after dynamically recomputing depth while rendering render: >function render(window) > window:clear() > local lines, cols = window:getmaxyx() > local bg=1 > local y, x = 0, 0 -- units of characters (0-based) > local w, h = 1, 1 -- units of zettels (1-based) > -- render zettels depth-first, while tracking relative positions > local done = {} > local inprogress = {{id=zettels.root,depth=0}} > render_state.wh2id = {{}} > while #inprogress > 0 do > local curr = table.remove(inprogress) > if not done[curr.id] then > done[curr.id] = true > table.insert(render_state.wh2id[w], curr.id) > local zettel = zettels[curr.id] > if curr.id == current_zettel_id then > render_state.curr_w = w > render_state.curr_h = h > end > local currbg = (curr.id == current_zettel_id) and view_settings.current_zettel_bg or bg > render_zettel(window, currbg, curr.depth * view_settings.indent, y, x, zettel) > if zettel.next then table.insert(inprogress, {id=zettel.next, depth=curr.depth}) end > if zettel.child then table.insert(inprogress, {id=zettel.child, depth=curr.depth+1}) end > if zettel.crosslinks then > for relation, target in pairs(zettel.crosslinks) do > table.insert(inprogress, {id=target, depth=curr.depth+1}) > end > end > bg = 3 - bg -- toggle between color pairs 1 and 2 > y = y + view_settings.height + view_settings.vmargin > h = h + 1 > if y + view_settings.height > lines then > y = 0 > h = 1 > x = x + view_settings.width + view_settings.hmargin > w = w + 1 > if x + view_settings.width > cols then break end > table.insert(render_state.wh2id, {}) > end > end > end > window:mvaddstr(lines-1, 0, '') > bg = 1 > x = 0 > for i=1,3 do > local zettel = nil > if i == 1 and stash then > zettel = zettels[stash] > end > render_zettel(window, bg, 0, lines-1, x, zettel) > bg = 3 - bg > x = x + view_settings.width + view_settings.hmargin > end > window:refresh() >end - __teliva_timestamp: >Thu Feb 10 20:55:19 2022 render_zettel: >function render_zettel(window, bg, indent, edge_label, starty, startx, zettel) > window:attrset(curses.color_pair(bg)) > for y=0,view_settings.height-1 do > for x=0,view_settings.width-1 do > window:mvaddch(y+starty, x+startx, ' ') > end > end > if indent > 1 then > window:attrset(curses.color_pair(bg+1)) -- go from zettel color to its edge color > window:mvaddstr(starty, startx+indent-1, edge_label) > window:attrset(curses.color_pair(bg)) > end > local y, x = 0, indent+1 > local data = '' > if zettel then > data = zettel.data > end > for i=1,#data do > local c = data[i] > if c == '\n' then > y = y+1 > x = indent+1 > else > window:mvaddstr(y+starty, x+startx, c) > x = x+1 > if x >= startx + view_settings.width then > y = y+1 > x = indent+1 > end > end > if y >= view_settings.height then > break > end > end >end - __teliva_timestamp: >Thu Feb 10 20:58:49 2022 view_settings: >view_settings = { > -- dimensions for rendering a single zettel; extra text gets truncated > width=50, > height=3, > -- spacing between zettels > hmargin=1, > vmargin=1, > -- > indent=2, -- how children of a zettel are indicated >} - __teliva_timestamp: >Thu Feb 10 20:59:18 2022 render: >function render(window) > window:clear() > local lines, cols = window:getmaxyx() > local bg=3 > local y, x = 0, 0 -- units of characters (0-based) > local w, h = 1, 1 -- units of zettels (1-based) > -- render zettels depth-first, while tracking relative positions > local done = {} > local inprogress = {{id=zettels.root,depth=0,edge=''}} > render_state.wh2id = {{}} > while #inprogress > 0 do > local curr = table.remove(inprogress) > if not done[curr.id] then > done[curr.id] = true > table.insert(render_state.wh2id[w], curr.id) > local zettel = zettels[curr.id] > if curr.id == current_zettel_id then > render_state.curr_w = w > render_state.curr_h = h > end > local currbg = (curr.id == current_zettel_id) and 1 or bg -- 1 is the color pair for the current zettel > render_zettel(window, currbg, curr.depth * view_settings.indent, curr.edge, y, x, zettel) > if zettel.next then table.insert(inprogress, {id=zettel.next, depth=curr.depth, edge='|'}) end > if zettel.child then table.insert(inprogress, {id=zettel.child, depth=curr.depth+1, edge='\\'}) end > if zettel.crosslinks then > for relation, target in pairs(zettel.crosslinks) do > table.insert(inprogress, {id=target, depth=curr.depth+1, edge=relation}) > end > end > bg = 8 - bg -- toggle between color pairs 3 and 5 > y = y + view_settings.height + view_settings.vmargin > h = h + 1 > if y + view_settings.height > lines then > y = 0 > h = 1 > x = x + view_settings.width + view_settings.hmargin > w = w + 1 > if x + view_settings.width > cols then break end > table.insert(render_state.wh2id, {}) > end > end > end > window:mvaddstr(lines-1, 0, '') > bg = 3 > x = 0 > for i=1,3 do > local zettel = nil > if i == 1 and stash then > zettel = zettels[stash] > end > render_zettel(window, bg, 0, '', lines-1, x, zettel) > bg = 8 - bg -- toggle between color pairs 3 and 5 > x = x + view_settings.width + view_settings.hmargin > end > window:refresh() >end - __teliva_timestamp: >Thu Feb 10 21:02:41 2022 __teliva_note: >label the incoming edge for each zettel > >Is it a child, sibling or other cross-link? init_colors: >function init_colors() > -- light background > -- current zettel > curses.init_pair(1, 236, 230) > curses.init_pair(2, 1, 230) -- edge label for current zettel > -- non-current zettel #1 > curses.init_pair(3, 236, 250) > curses.init_pair(4, 1, 250) -- edge label for pair 3 > -- non-current zettel #2 > curses.init_pair(5, 236, 252) > curses.init_pair(6, 1, 252) -- edge label for pair 5 > -- dark background >--? -- current zettel >--? curses.init_pair(7, 252, 130) >--? -- other zettels >--? curses.init_pair(1, 252, 240) >--? curses.init_pair(2, 252, 242) >--? -- edge labels >--? curses.init_pair(3, 1, 240) -- same bg as pair 1 >--? curses.init_pair(4, 1, 242) -- same bg as pair 2 >--? curses.init_pair(9, 1, 130) -- same bg as pair 7 for current zettel >end - __teliva_timestamp: >Thu Feb 10 21:11:35 2022 menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = { > {'a,b,c', 'insert'}, > {'e', 'edit'}, > {'j,k,l,h', 'move'}, > {'x,X,y,Y', 'resize'}, > {'s', 'stash'}, > {'t', 'link with stash'}, > {'z', 'scroll'}, >} - __teliva_timestamp: >Thu Feb 10 21:13:19 2022 main: >function main() > init_colors() > curses.curs_set(0) -- hide cursor except when editing > > local infile = io.open('zet', 'r') > if infile then > read_zettels(infile) > end > current_zettel_id = zettels.root -- cursor > view_settings.first_zettel = zettels.root -- start rendering here > > while true do > render(Window) > update(Window) > > -- save zettels, but hold on to previous state on disk > -- until last possible second > local filename = os.tmpname() > local outfile = io.open(filename, 'w') > if outfile then > write_zettels(outfile) > os.rename(filename, 'zet') > end > end >end - __teliva_timestamp: >Thu Feb 10 21:13:36 2022 render: >function render(window) > window:clear() > local lines, cols = window:getmaxyx() > local bg=3 > local y, x = 0, 0 -- units of characters (0-based) > local w, h = 1, 1 -- units of zettels (1-based) > -- render zettels depth-first, while tracking relative positions > local done = {} > local inprogress = {{id=view_settings.first_zettel,depth=0,edge=''}} > render_state.wh2id = {{}} > while #inprogress > 0 do > local curr = table.remove(inprogress) > if not done[curr.id] then > done[curr.id] = true > table.insert(render_state.wh2id[w], curr.id) > local zettel = zettels[curr.id] > if curr.id == current_zettel_id then > render_state.curr_w = w > render_state.curr_h = h > end > local currbg = (curr.id == current_zettel_id) and 1 or bg -- 1 is the color pair for the current zettel > render_zettel(window, currbg, curr.depth * view_settings.indent, curr.edge, y, x, zettel) > if zettel.next then table.insert(inprogress, {id=zettel.next, depth=curr.depth, edge='|'}) end > if zettel.child then table.insert(inprogress, {id=zettel.child, depth=curr.depth+1, edge='\\'}) end > if zettel.crosslinks then > for relation, target in pairs(zettel.crosslinks) do > table.insert(inprogress, {id=target, depth=curr.depth+1, edge=relation}) > end > end > bg = 8 - bg -- toggle between color pairs 3 and 5 > y = y + view_settings.height + view_settings.vmargin > h = h + 1 > if y + view_settings.height > lines then > y = 0 > h = 1 > x = x + view_settings.width + view_settings.hmargin > w = w + 1 > if x + view_settings.width > cols then break end > table.insert(render_state.wh2id, {}) > end > end > end > window:mvaddstr(lines-1, 0, '') > bg = 3 > x = 0 > for i=1,3 do > local zettel = nil > if i == 1 and stash then > zettel = zettels[stash] > end > render_zettel(window, bg, 0, '', lines-1, x, zettel) > bg = 8 - bg -- toggle between color pairs 3 and 5 > x = x + view_settings.width + view_settings.hmargin > end > window:refresh() >end - __teliva_timestamp: >Thu Feb 10 21:19:26 2022 __teliva_note: >bugfix: cross-links should be bidirectional update: >function update(window) > local key = window:getch() > local h, w = window:getmaxyx() > local curr = zettels[current_zettel_id] > assert(curr, string.format('cursor fell off the edge of the world: %s', type(current_zettel_id))) > -- move along the graph > if key == string.byte('j') then > if curr.child then > current_zettel_id = curr.child > elseif curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > elseif key == string.byte('k') then > if curr.parent then current_zettel_id = curr.parent end > elseif key == string.byte('h') then > if curr.prev then > current_zettel_id = curr.prev > elseif curr.parent then > current_zettel_id = curr.parent > end > elseif key == string.byte('l') then > if curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > -- move along the screen > elseif key == curses.KEY_UP then > if render_state.curr_h > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h - 1] > end > elseif key == curses.KEY_DOWN then > if render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] > end > elseif key == curses.KEY_LEFT then > if render_state.curr_w > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w - 1][render_state.curr_h] > end > elseif key == curses.KEY_RIGHT then > if render_state.wh2id[render_state.curr_w + 1] and render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] then > current_zettel_id = render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] > end > -- mutations > elseif key == string.byte('e') then > editz(window) > elseif key == string.byte('a') then > -- insert sibling after > local old = curr.next > curr.next = new_id() > local new = zettels[curr.next] > new.data = '' > new.next = old > new.prev = current_zettel_id > if old then > zettels[old].prev = curr.next > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > end > new.parent = curr.parent > current_zettel_id = curr.next > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('b') then > -- insert sibling before > local old = curr.prev > curr.prev = new_id() > local new = zettels[curr.prev] > new.data = '' > new.prev = old > new.next = current_zettel_id > if old then > zettels[old].next = curr.prev > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > end > new.parent = curr.parent > current_zettel_id = curr.prev > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('c') then > -- insert child > local old = curr.child > curr.child = new_id() > local new = zettels[curr.child] > new.data = '' > new.next = old > if old then > assert(zettels[old].prev == nil, "first child shouldn't have a previous sibling") > zettels[old].prev = curr.child > end > new.parent = curr > current_zettel_id = curr.child > render(window) -- recompute render_state > editz(window) > -- cross-links > elseif key == string.byte('s') then > -- save zettel to a stash > stash = current_zettel_id > elseif key == string.byte('t') then > -- cross-link a zettel bidirectionally with what's on the stash > local insert_crosslink = > function(a, rel, b_id) > if a.crosslinks == nil then > a.crosslinks = {} > end > a.crosslinks[rel] = b_id > end > insert_crosslink(curr, 'a', stash) > insert_crosslink(zettels[stash], 'a', current_zettel_id) > -- view settings > elseif key == string.byte('x') then > if view_settings.width > 5 then > view_settings.width = view_settings.width - 5 > end > elseif key == string.byte('X') then > if view_settings.width < w-5 then > view_settings.width = view_settings.width + 5 > end > elseif key == string.byte('y') then > if view_settings.height > 0 then > view_settings.height = view_settings.height - 1 > end > elseif key == string.byte('Y') then > if view_settings.height < h-2 then > view_settings.height = view_settings.height + 1 > end > elseif key == string.byte('z') then > -- scroll to show the current zettel at top of screen > -- often has the effect of zooming in on its hierarchy > view_settings.first_zettel = current_zettel_id > end >end - __teliva_timestamp: >Thu Feb 10 21:20:45 2022 __teliva_note: >clear stash after linking update: >function update(window) > local key = window:getch() > local h, w = window:getmaxyx() > local curr = zettels[current_zettel_id] > assert(curr, string.format('cursor fell off the edge of the world: %s', type(current_zettel_id))) > -- move along the graph > if key == string.byte('j') then > if curr.child then > current_zettel_id = curr.child > elseif curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > elseif key == string.byte('k') then > if curr.parent then current_zettel_id = curr.parent end > elseif key == string.byte('h') then > if curr.prev then > current_zettel_id = curr.prev > elseif curr.parent then > current_zettel_id = curr.parent > end > elseif key == string.byte('l') then > if curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > -- move along the screen > elseif key == curses.KEY_UP then > if render_state.curr_h > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h - 1] > end > elseif key == curses.KEY_DOWN then > if render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] > end > elseif key == curses.KEY_LEFT then > if render_state.curr_w > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w - 1][render_state.curr_h] > end > elseif key == curses.KEY_RIGHT then > if render_state.wh2id[render_state.curr_w + 1] and render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] then > current_zettel_id = render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] > end > -- mutations > elseif key == string.byte('e') then > editz(window) > elseif key == string.byte('a') then > -- insert sibling after > local old = curr.next > curr.next = new_id() > local new = zettels[curr.next] > new.data = '' > new.next = old > new.prev = current_zettel_id > if old then > zettels[old].prev = curr.next > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > end > new.parent = curr.parent > current_zettel_id = curr.next > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('b') then > -- insert sibling before > local old = curr.prev > curr.prev = new_id() > local new = zettels[curr.prev] > new.data = '' > new.prev = old > new.next = current_zettel_id > if old then > zettels[old].next = curr.prev > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > end > new.parent = curr.parent > current_zettel_id = curr.prev > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('c') then > -- insert child > local old = curr.child > curr.child = new_id() > local new = zettels[curr.child] > new.data = '' > new.next = old > if old then > assert(zettels[old].prev == nil, "first child shouldn't have a previous sibling") > zettels[old].prev = curr.child > end > new.parent = curr > current_zettel_id = curr.child > render(window) -- recompute render_state > editz(window) > -- cross-links > elseif key == string.byte('s') then > -- save zettel to a stash > stash = current_zettel_id > elseif key == string.byte('t') then > -- cross-link a zettel bidirectionally with what's on the stash > local insert_crosslink = > function(a, rel, b_id) > if a.crosslinks == nil then > a.crosslinks = {} > end > a.crosslinks[rel] = b_id > end > insert_crosslink(curr, 'a', stash) > insert_crosslink(zettels[stash], 'a', current_zettel_id) > stash = nil > -- view settings > elseif key == string.byte('x') then > if view_settings.width > 5 then > view_settings.width = view_settings.width - 5 > end > elseif key == string.byte('X') then > if view_settings.width < w-5 then > view_settings.width = view_settings.width + 5 > end > elseif key == string.byte('y') then > if view_settings.height > 0 then > view_settings.height = view_settings.height - 1 > end > elseif key == string.byte('Y') then > if view_settings.height < h-2 then > view_settings.height = view_settings.height + 1 > end > elseif key == string.byte('z') then > -- scroll to show the current zettel at top of screen > -- often has the effect of zooming in on its hierarchy > view_settings.first_zettel = current_zettel_id > end >end - __teliva_timestamp: >Thu Feb 10 21:51:09 2022 __teliva_note: >fix regression in editor editz_render: >function editz_render(window, s, cursor, top, minbottom, left, right) > local h, w = window:getmaxyx() > local cursor_y, cursor_x = 0, 0 > window:attrset(curses.color_pair(1)) -- 1 is the color combination for the current zettel > for y=top,minbottom-1 do > for x=left,right-1 do > window:mvaddch(y, x, ' ') > end > end > local y, x = top, left + 1 -- left padding; TODO: indent > window:mvaddstr(y, x, '') > for i=1,s:len() do > if i == cursor then > cursor_y = y > cursor_x = x > end > if s[i] ~= '\n' then > window:addstr(s[i]) > x = x + 1 > if x >= right then > y = y + 1 > if y >= h-2 then return end > x = left + 1 -- left padding; TODO: indent > window:mvaddstr(y, x, '') > end > else > for col=x+1,right-1 do window:addch(' '); end > x = left > y = y + 1 > if y >= h-2 then return end > window:mvaddstr(y, x, '') > for col=x,right-1 do window:addch(' '); end > x = left + 1 -- left padding; TODO: indent > window:mvaddstr(y, x, '') > end > end > if cursor_y == 0 and cursor_x == 0 then > cursor_y = y > cursor_x = x > end > window:mvaddstr(cursor_y, cursor_x, '') >end - __teliva_timestamp: >Fri Feb 11 01:33:31 2022 __teliva_note: >support /tmp being on a separate volume > >also better error-checking main: >function main() > init_colors() > curses.curs_set(0) -- hide cursor except when editing > > local infile = io.open('zet', 'r') > if infile then > read_zettels(infile) > end > current_zettel_id = zettels.root -- cursor > view_settings.first_zettel = zettels.root -- start rendering here > > while true do > render(Window) > update(Window) > > -- save zettels, but hold on to previous state on disk > -- until last possible second > local outfile = io.open('teliva_tmp', 'w') > if outfile then > write_zettels(outfile) > local status, message = os.rename('teliva_tmp', 'zet') > assert(status, message) -- unceremoniously abort, but we hopefully only lost a little > end > -- TODO: what if io.open failed for a non-sandboxing related reason?! > -- We could silently fail to save. > end >end - __teliva_timestamp: >Fri Feb 11 07:51:42 2022 __teliva_note: >bugfix in parent link when inserting child update: >function update(window) > local key = window:getch() > local h, w = window:getmaxyx() > local curr = zettels[current_zettel_id] > assert(curr, string.format('cursor fell off the edge of the world: %s', type(current_zettel_id))) > -- move along the graph > if key == string.byte('j') then > if curr.child then > current_zettel_id = curr.child > elseif curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > elseif key == string.byte('k') then > if curr.parent then > current_zettel_id = curr.parent > end > elseif key == string.byte('h') then > if curr.prev then > current_zettel_id = curr.prev > elseif curr.parent then > current_zettel_id = curr.parent > end > elseif key == string.byte('l') then > if curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > -- move along the screen > elseif key == curses.KEY_UP then > if render_state.curr_h > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h - 1] > end > elseif key == curses.KEY_DOWN then > if render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] > end > elseif key == curses.KEY_LEFT then > if render_state.curr_w > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w - 1][render_state.curr_h] > end > elseif key == curses.KEY_RIGHT then > if render_state.wh2id[render_state.curr_w + 1] and render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] then > current_zettel_id = render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] > end > -- mutations > elseif key == string.byte('e') then > editz(window) > elseif key == string.byte('a') then > -- insert sibling after > local old = curr.next > curr.next = new_id() > local new = zettels[curr.next] > new.data = '' > new.next = old > new.prev = current_zettel_id > if old then > zettels[old].prev = curr.next > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > end > new.parent = curr.parent > current_zettel_id = curr.next > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('b') then > -- insert sibling before > local old = curr.prev > curr.prev = new_id() > local new = zettels[curr.prev] > new.data = '' > new.prev = old > new.next = current_zettel_id > if old then > zettels[old].next = curr.prev > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > end > new.parent = curr.parent > current_zettel_id = curr.prev > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('c') then > -- insert child > local old = curr.child > curr.child = new_id() > local new = zettels[curr.child] > new.data = '' > new.next = old > if old then > assert(zettels[old].prev == nil, "first child shouldn't have a previous sibling") > zettels[old].prev = curr.child > end > new.parent = current_zettel_id > current_zettel_id = curr.child > render(window) -- recompute render_state > editz(window) > -- cross-links > elseif key == string.byte('s') then > -- save zettel to a stash > stash = current_zettel_id > elseif key == string.byte('t') then > -- cross-link a zettel bidirectionally with what's on the stash > local insert_crosslink = > function(a, rel, b_id) > if a.crosslinks == nil then > a.crosslinks = {} > end > a.crosslinks[rel] = b_id > end > insert_crosslink(curr, 'a', stash) > insert_crosslink(zettels[stash], 'a', current_zettel_id) > stash = nil > -- view settings > elseif key == string.byte('x') then > if view_settings.width > 5 then > view_settings.width = view_settings.width - 5 > end > elseif key == string.byte('X') then > if view_settings.width < w-5 then > view_settings.width = view_settings.width + 5 > end > elseif key == string.byte('y') then > if view_settings.height > 0 then > view_settings.height = view_settings.height - 1 > end > elseif key == string.byte('Y') then > if view_settings.height < h-2 then > view_settings.height = view_settings.height + 1 > end > elseif key == string.byte('z') then > -- scroll to show the current zettel at top of screen > -- often has the effect of zooming in on its hierarchy > view_settings.first_zettel = current_zettel_id > end >end - __teliva_timestamp: >Sat Feb 12 15:11:15 2022 editz_update: >function editz_update(window, prose, cursor, original_prose) > local key = window:getch() > local h, w = window:getmaxyx() > -- cursor movement > if key == curses.KEY_LEFT then > if cursor > 1 then > cursor = cursor-1 > end > elseif key == curses.KEY_RIGHT then > if cursor <= #prose then > cursor = cursor+1 > end > elseif key == curses.KEY_DOWN then > cursor = cursor_down(prose, cursor, w) > elseif key == curses.KEY_UP then > cursor = cursor_up(prose, cursor, w) > elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then -- ctrl-h, ctrl-?, delete > if cursor > 1 then > cursor = cursor-1 > prose = prose:remove(cursor) > end > elseif key == 1 then -- ctrl-a > elseif key == 12 then -- ctrl-l > elseif key == 6 then -- ctrl-f > elseif key == 2 then -- ctrl-b > -- delete > elseif key == 11 then -- ctrl-k > -- exit > elseif key == 5 then -- ctrl-e > return true, prose, cursor > elseif key == 7 then -- ctrl-g > return true, original_prose, cursor > -- insert > elseif key == 10 or (key >= 32 and key < 127) then > prose = prose:insert(string.char(key), cursor-1) > cursor = cursor+1 > end > return false, prose, cursor >end - __teliva_timestamp: >Sat Feb 12 15:11:33 2022 editz: >function editz(window) > local old_menu = menu > menu = { > {'^e', 'finish edit'}, > {'^g', 'cancel edit'}, > {'^a', '< {'^b', ' {'^f', 'word>'}, > {'^l', 'line>>'}, > {'^k', 'del to line>>'}, > } > local old_data = zettels[current_zettel_id].data:sub(1) > local top = (render_state.curr_h - 1) * (view_settings.height + view_settings.vmargin) > local bottom = top + view_settings.height > local left = (render_state.curr_w - 1) * (view_settings.width + view_settings.hmargin) > local right = left + view_settings.width > local cursor = zettels[current_zettel_id].data:len()+1 > local quit = false > curses.curs_set(1) > while not quit do > editz_render(window, zettels[current_zettel_id].data, cursor, top, bottom, left, right) > quit, zettels[current_zettel_id].data, cursor = editz_update(window, zettels[current_zettel_id].data, cursor, old_data) > end > curses.curs_set(0) > menu = old_menu >end - __teliva_timestamp: >Sat Feb 12 15:55:10 2022 __teliva_note: >editor: move to start of line, move/delete to end of line editz_update: >function editz_update(window, prose, cursor, original_prose) > local key = window:getch() > local h, w = window:getmaxyx() > -- cursor movement > if key == curses.KEY_LEFT then > if cursor > 1 then > cursor = cursor-1 > end > elseif key == curses.KEY_RIGHT then > if cursor <= #prose then > cursor = cursor+1 > end > elseif key == curses.KEY_DOWN then > cursor = cursor_down(prose, cursor, w) > elseif key == curses.KEY_UP then > cursor = cursor_up(prose, cursor, w) > elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then -- ctrl-h, ctrl-?, delete > if cursor > 1 then > cursor = cursor-1 > prose = prose:remove(cursor) > end > elseif key == 1 then -- ctrl-a > while cursor > 1 do > if prose[cursor-1] == '\n' then break end > cursor = cursor-1 > end > elseif key == 12 then -- ctrl-l > local max = prose:len() > while cursor <= max and prose[cursor] ~= '\n' do > cursor = cursor+1 > end > elseif key == 6 then -- ctrl-f > elseif key == 2 then -- ctrl-b > -- delete > elseif key == 11 then -- ctrl-k > while cursor <= prose:len() and prose[cursor] ~= '\n' do > prose = prose:remove(cursor) > end > -- exit > elseif key == 5 then -- ctrl-e > return true, prose, cursor > elseif key == 7 then -- ctrl-g > return true, original_prose, cursor > -- insert > elseif key == 10 or (key >= 32 and key < 127) then > prose = prose:insert(string.char(key), cursor-1) > cursor = cursor+1 > end > return false, prose, cursor >end - __teliva_timestamp: >Sat Feb 12 17:01:45 2022 __teliva_note: >editor: word-movement shortcuts editz_update: >function editz_update(window, prose, cursor, original_prose) > local key = window:getch() > local h, w = window:getmaxyx() > -- cursor movement > if key == curses.KEY_LEFT then > if cursor > 1 then > cursor = cursor-1 > end > elseif key == curses.KEY_RIGHT then > if cursor <= #prose then > cursor = cursor+1 > end > elseif key == curses.KEY_DOWN then > cursor = cursor_down(prose, cursor, w) > elseif key == curses.KEY_UP then > cursor = cursor_up(prose, cursor, w) > elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then -- ctrl-h, ctrl-?, delete > if cursor > 1 then > cursor = cursor-1 > prose = prose:remove(cursor) > end > elseif key == 1 then -- ctrl-a > -- to start of line > while cursor > 1 do > if prose[cursor-1] == '\n' then break end > cursor = cursor-1 > end > elseif key == 12 then -- ctrl-l > -- to end of line > local max = prose:len() > while cursor <= max and prose[cursor] ~= '\n' do > cursor = cursor+1 > end > elseif key == 6 then -- ctrl-f > -- to next word > local max = prose:len() > while cursor <= max and prose[cursor]:match('%w') do > cursor = cursor+1 > end > while cursor <= max and prose[cursor]:match('%W') do > cursor = cursor+1 > end > elseif key == 2 then -- ctrl-b > -- to previous word > if cursor > prose:len() then > cursor = prose:len() > end > while cursor > 1 and prose[cursor]:match('%W') do > cursor = cursor-1 > end > while cursor > 1 and prose[cursor]:match('%w') do > cursor = cursor-1 > end > -- delete > elseif key == 11 then -- ctrl-k > while cursor <= prose:len() and prose[cursor] ~= '\n' do > prose = prose:remove(cursor) > end > -- exit > elseif key == 5 then -- ctrl-e > return true, prose, cursor > elseif key == 7 then -- ctrl-g > return true, original_prose, cursor > -- insert > elseif key == 10 or (key >= 32 and key < 127) then > prose = prose:insert(string.char(key), cursor-1) > cursor = cursor+1 > end > return false, prose, cursor >end - __teliva_timestamp: >Sat Feb 12 17:12:27 2022 editz_render: >function editz_render(window, s, cursor, top, minbottom, left, right) > local h, w = window:getmaxyx() > local cursor_y, cursor_x = 0, 0 > window:attrset(curses.color_pair(1)) -- 1 is the color combination for the current zettel > for y=top,minbottom-1 do > for x=left,right-1 do > window:mvaddch(y, x, ' ') > end > end > for x=left,right-1 do > window:mvaddch(minbottom, x, ' ') > end > local y, x = top, left + 1 -- left padding; TODO: indent > window:mvaddstr(y, x, '') > for i=1,s:len() do > if i == cursor then > cursor_y = y > cursor_x = x > end > if s[i] ~= '\n' then > window:addstr(s[i]) > x = x + 1 > if x >= right then > y = y + 1 > if y >= h-2 then return end > x = left + 1 -- left padding; TODO: indent > window:mvaddstr(y, x, '') > end > else > for col=x+1,right-1 do window:addch(' '); end > x = left > y = y + 1 > if y >= h-2 then return end > window:mvaddstr(y, x, '') > for col=x,right-1 do window:addch(' '); end > x = left + 1 -- left padding; TODO: indent > window:mvaddstr(y, x, '') > end > end > if cursor_y == 0 and cursor_x == 0 then > cursor_y = y > cursor_x = x > end > window:mvaddstr(cursor_y, cursor_x, '') >end - __teliva_timestamp: >Sat Feb 12 17:15:15 2022 render_state: >-- some information about what's been drawn on screen >render_state = { > -- where the current zettel is, in units of zettels > curr_h = 1, > curr_w = 1, > -- what zettel is at each position on screen, in units of zettels > hw2id = {}, > -- list of zettels currently displayed > displayed = {}, >} - __teliva_timestamp: >Sat Feb 12 17:16:20 2022 render: >function render(window) > window:clear() > local lines, cols = window:getmaxyx() > local bg=3 > local y, x = 0, 0 -- units of characters (0-based) > local w, h = 1, 1 -- units of zettels (1-based) > -- render zettels depth-first, while tracking relative positions > local done = {} > local inprogress = {{id=view_settings.first_zettel,depth=0,edge=''}} > render_state.wh2id = {{}} > render_state.displayed = {} > while #inprogress > 0 do > local curr = table.remove(inprogress) > if not done[curr.id] then > done[curr.id] = true > render_state.displayed[curr.id] = true > table.insert(render_state.wh2id[w], curr.id) > local zettel = zettels[curr.id] > if curr.id == current_zettel_id then > render_state.curr_w = w > render_state.curr_h = h > end > local currbg = (curr.id == current_zettel_id) and 1 or bg -- 1 is the color pair for the current zettel > render_zettel(window, currbg, curr.depth * view_settings.indent, curr.edge, y, x, zettel) > if zettel.next then table.insert(inprogress, {id=zettel.next, depth=curr.depth, edge='|'}) end > if zettel.child then table.insert(inprogress, {id=zettel.child, depth=curr.depth+1, edge='\\'}) end > if zettel.crosslinks then > for relation, target in pairs(zettel.crosslinks) do > table.insert(inprogress, {id=target, depth=curr.depth+1, edge=relation}) > end > end > bg = 8 - bg -- toggle between color pairs 3 and 5 > y = y + view_settings.height + view_settings.vmargin > h = h + 1 > if y + view_settings.height > lines then > y = 0 > h = 1 > x = x + view_settings.width + view_settings.hmargin > w = w + 1 > if x + view_settings.width > cols then break end > table.insert(render_state.wh2id, {}) > end > end > end > window:mvaddstr(lines-1, 0, '') > bg = 3 > x = 0 > for i=1,3 do > local zettel = nil > if i == 1 and stash then > zettel = zettels[stash] > end > render_zettel(window, bg, 0, '', lines-1, x, zettel) > bg = 8 - bg -- toggle between color pairs 3 and 5 > x = x + view_settings.width + view_settings.hmargin > end > window:refresh() >end - __teliva_timestamp: >Sat Feb 12 17:18:34 2022 __teliva_note: >scroll as needed when moving along the graph update: >function update(window) > local key = window:getch() > local h, w = window:getmaxyx() > local curr = zettels[current_zettel_id] > assert(curr, string.format('cursor fell off the edge of the world: %s', type(current_zettel_id))) > -- move along the graph > if key == string.byte('j') then > if curr.child then > current_zettel_id = curr.child > elseif curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > -- scroll if necessary > if not render_state.displayed[current_zettel_id] then > view_settings.first_zettel = current_zettel_id > end > elseif key == string.byte('k') then > if curr.parent then > current_zettel_id = curr.parent > end > -- scroll if necessary > if not render_state.displayed[current_zettel_id] then > view_settings.first_zettel = current_zettel_id > end > elseif key == string.byte('h') then > if curr.prev then > current_zettel_id = curr.prev > elseif curr.parent then > current_zettel_id = curr.parent > end > -- scroll if necessary > if not render_state.displayed[current_zettel_id] then > view_settings.first_zettel = current_zettel_id > end > elseif key == string.byte('l') then > if curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > -- scroll if necessary > if not render_state.displayed[current_zettel_id] then > view_settings.first_zettel = current_zettel_id > end > -- move along the screen > elseif key == curses.KEY_UP then > if render_state.curr_h > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h - 1] > end > elseif key == curses.KEY_DOWN then > if render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] > end > elseif key == curses.KEY_LEFT then > if render_state.curr_w > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w - 1][render_state.curr_h] > end > elseif key == curses.KEY_RIGHT then > if render_state.wh2id[render_state.curr_w + 1] and render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] then > current_zettel_id = render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] > end > -- mutations > elseif key == string.byte('e') then > editz(window) > elseif key == string.byte('a') then > -- insert sibling after > local old = curr.next > curr.next = new_id() > local new = zettels[curr.next] > new.data = '' > new.next = old > new.prev = current_zettel_id > if old then > zettels[old].prev = curr.next > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > end > new.parent = curr.parent > current_zettel_id = curr.next > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('b') then > -- insert sibling before > local old = curr.prev > curr.prev = new_id() > local new = zettels[curr.prev] > new.data = '' > new.prev = old > new.next = current_zettel_id > if old then > zettels[old].next = curr.prev > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > end > new.parent = curr.parent > current_zettel_id = curr.prev > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('c') then > -- insert child > local old = curr.child > curr.child = new_id() > local new = zettels[curr.child] > new.data = '' > new.next = old > if old then > assert(zettels[old].prev == nil, "first child shouldn't have a previous sibling") > zettels[old].prev = curr.child > end > new.parent = current_zettel_id > current_zettel_id = curr.child > render(window) -- recompute render_state > editz(window) > -- cross-links > elseif key == string.byte('s') then > -- save zettel to a stash > stash = current_zettel_id > elseif key == string.byte('t') then > -- cross-link a zettel bidirectionally with what's on the stash > local insert_crosslink = > function(a, rel, b_id) > if a.crosslinks == nil then > a.crosslinks = {} > end > a.crosslinks[rel] = b_id > end > insert_crosslink(curr, 'a', stash) > insert_crosslink(zettels[stash], 'a', current_zettel_id) > stash = nil > -- view settings > elseif key == string.byte('x') then > if view_settings.width > 5 then > view_settings.width = view_settings.width - 5 > end > elseif key == string.byte('X') then > if view_settings.width < w-5 then > view_settings.width = view_settings.width + 5 > end > elseif key == string.byte('y') then > if view_settings.height > 0 then > view_settings.height = view_settings.height - 1 > end > elseif key == string.byte('Y') then > if view_settings.height < h-2 then > view_settings.height = view_settings.height + 1 > end > elseif key == string.byte('z') then > -- scroll to show the current zettel at top of screen > -- often has the effect of zooming in on its hierarchy > view_settings.first_zettel = current_zettel_id > end >end - __teliva_timestamp: >Sat Feb 12 17:23:33 2022 __teliva_note: >editor 'k' shortcut: fall back to next sibling if needed > >Now we should be able to navigate either with j/k or h/l. update: >function update(window) > local key = window:getch() > local h, w = window:getmaxyx() > local curr = zettels[current_zettel_id] > assert(curr, string.format('cursor fell off the edge of the world: %s', type(current_zettel_id))) > -- move along the graph > if key == string.byte('j') then > -- child or next sibling > if curr.child then > current_zettel_id = curr.child > elseif curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > -- scroll if necessary > if not render_state.displayed[current_zettel_id] then > view_settings.first_zettel = current_zettel_id > end > elseif key == string.byte('k') then > -- parent or previous sibling > if curr.parent then > current_zettel_id = curr.parent > elseif curr.prev then > current_zettel_id = curr.prev > end > -- scroll if necessary > if not render_state.displayed[current_zettel_id] then > view_settings.first_zettel = current_zettel_id > end > elseif key == string.byte('h') then > -- previous sibling or parent > if curr.prev then > current_zettel_id = curr.prev > elseif curr.parent then > current_zettel_id = curr.parent > end > -- scroll if necessary > if not render_state.displayed[current_zettel_id] then > view_settings.first_zettel = current_zettel_id > end > elseif key == string.byte('l') then > -- next sibling or next sibling of parent > if curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > -- scroll if necessary > if not render_state.displayed[current_zettel_id] then > view_settings.first_zettel = current_zettel_id > end > -- move along the screen > elseif key == curses.KEY_UP then > if render_state.curr_h > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h - 1] > end > elseif key == curses.KEY_DOWN then > if render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] > end > elseif key == curses.KEY_LEFT then > if render_state.curr_w > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w - 1][render_state.curr_h] > end > elseif key == curses.KEY_RIGHT then > if render_state.wh2id[render_state.curr_w + 1] and render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] then > current_zettel_id = render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] > end > -- mutations > elseif key == string.byte('e') then > editz(window) > elseif key == string.byte('a') then > -- insert sibling after > local old = curr.next > curr.next = new_id() > local new = zettels[curr.next] > new.data = '' > new.next = old > new.prev = current_zettel_id > if old then > zettels[old].prev = curr.next > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > end > new.parent = curr.parent > current_zettel_id = curr.next > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('b') then > -- insert sibling before > local old = curr.prev > curr.prev = new_id() > local new = zettels[curr.prev] > new.data = '' > new.prev = old > new.next = current_zettel_id > if old then > zettels[old].next = curr.prev > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > end > new.parent = curr.parent > current_zettel_id = curr.prev > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('c') then > -- insert child > local old = curr.child > curr.child = new_id() > local new = zettels[curr.child] > new.data = '' > new.next = old > if old then > assert(zettels[old].prev == nil, "first child shouldn't have a previous sibling") > zettels[old].prev = curr.child > end > new.parent = current_zettel_id > current_zettel_id = curr.child > render(window) -- recompute render_state > editz(window) > -- cross-links > elseif key == string.byte('s') then > -- save zettel to a stash > stash = current_zettel_id > elseif key == string.byte('t') then > -- cross-link a zettel bidirectionally with what's on the stash > local insert_crosslink = > function(a, rel, b_id) > if a.crosslinks == nil then > a.crosslinks = {} > end > a.crosslinks[rel] = b_id > end > insert_crosslink(curr, 'a', stash) > insert_crosslink(zettels[stash], 'a', current_zettel_id) > stash = nil > -- view settings > elseif key == string.byte('x') then > if view_settings.width > 5 then > view_settings.width = view_settings.width - 5 > end > elseif key == string.byte('X') then > if view_settings.width < w-5 then > view_settings.width = view_settings.width + 5 > end > elseif key == string.byte('y') then > if view_settings.height > 0 then > view_settings.height = view_settings.height - 1 > end > elseif key == string.byte('Y') then > if view_settings.height < h-2 then > view_settings.height = view_settings.height + 1 > end > elseif key == string.byte('z') then > -- scroll to show the current zettel at top of screen > -- often has the effect of zooming in on its hierarchy > view_settings.first_zettel = current_zettel_id > end >end - __teliva_timestamp: >Sat Feb 12 17:27:18 2022 menu: >-- To show app-specific hotkeys in the menu bar, add hotkey/command >-- arrays of strings to the menu array. >menu = { > {'a,b,c', 'insert'}, > {'e', 'edit'}, > {'j,k,l,h', 'move'}, > {'<', 'back'}, > {'x,X,y,Y', 'resize'}, > {'s', 'stash'}, > {'t', 'link with stash'}, > {'z', 'scroll'}, >} - __teliva_timestamp: >Sat Feb 12 17:57:15 2022 update: >function update(window) > local key = window:getch() > local h, w = window:getmaxyx() > local curr = zettels[current_zettel_id] > assert(curr, string.format('cursor fell off the edge of the world: %s', type(current_zettel_id))) > -- read from or write to render_state.history > if key == string.byte('<') then > -- previous zettel moved to > -- does NOT undo mutations > if #render_state.history > 0 then > local previous_state = render_state.history[#render_state.history] > view_settings.first_zettel = previous_state.first_zettel > current_zettel_id = previous_state.cursor > table.remove(render_state.history) > end > return > end > if key ~= string.byte('e') then > table.insert(render_state.history, {first_zettel=view_settings.first_zettel, cursor=current_zettel_id}) > end > -- move along the graph > if key == string.byte('j') then > -- child or next sibling > if curr.child then > current_zettel_id = curr.child > elseif curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > -- scroll if necessary > if not render_state.displayed[current_zettel_id] then > view_settings.first_zettel = current_zettel_id > end > elseif key == string.byte('k') then > -- parent or previous sibling > if curr.parent then > current_zettel_id = curr.parent > elseif curr.prev then > current_zettel_id = curr.prev > end > -- scroll if necessary > if not render_state.displayed[current_zettel_id] then > view_settings.first_zettel = current_zettel_id > end > elseif key == string.byte('h') then > -- previous sibling or parent > if curr.prev then > current_zettel_id = curr.prev > elseif curr.parent then > current_zettel_id = curr.parent > end > -- scroll if necessary > if not render_state.displayed[current_zettel_id] then > view_settings.first_zettel = current_zettel_id > end > elseif key == string.byte('l') then > -- next sibling or next sibling of parent > if curr.next then > current_zettel_id = curr.next > elseif curr.parent and zettels[curr.parent].next then > current_zettel_id = zettels[curr.parent].next > end > -- scroll if necessary > if not render_state.displayed[current_zettel_id] then > view_settings.first_zettel = current_zettel_id > end > -- move along the screen > elseif key == curses.KEY_UP then > if render_state.curr_h > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h - 1] > end > elseif key == curses.KEY_DOWN then > if render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] then > current_zettel_id = render_state.wh2id[render_state.curr_w][render_state.curr_h + 1] > end > elseif key == curses.KEY_LEFT then > if render_state.curr_w > 1 then > current_zettel_id = render_state.wh2id[render_state.curr_w - 1][render_state.curr_h] > end > elseif key == curses.KEY_RIGHT then > if render_state.wh2id[render_state.curr_w + 1] and render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] then > current_zettel_id = render_state.wh2id[render_state.curr_w + 1][render_state.curr_h] > end > -- mutations > elseif key == string.byte('e') then > editz(window) > elseif key == string.byte('a') then > -- insert sibling after > local old = curr.next > curr.next = new_id() > local new = zettels[curr.next] > new.data = '' > new.next = old > new.prev = current_zettel_id > if old then > zettels[old].prev = curr.next > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > end > new.parent = curr.parent > current_zettel_id = curr.next > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('b') then > -- insert sibling before > local old = curr.prev > curr.prev = new_id() > local new = zettels[curr.prev] > new.data = '' > new.prev = old > new.next = current_zettel_id > if old then > zettels[old].next = curr.prev > assert(curr.parent == zettels[old].parent, 'siblings should have same parent') > end > new.parent = curr.parent > current_zettel_id = curr.prev > render(window) -- recompute render_state > editz(window) > elseif key == string.byte('c') then > -- insert child > local old = curr.child > curr.child = new_id() > local new = zettels[curr.child] > new.data = '' > new.next = old > if old then > assert(zettels[old].prev == nil, "first child shouldn't have a previous sibling") > zettels[old].prev = curr.child > end > new.parent = current_zettel_id > current_zettel_id = curr.child > render(window) -- recompute render_state > editz(window) > -- cross-links > elseif key == string.byte('s') then > -- save zettel to a stash > stash = current_zettel_id > elseif key == string.byte('t') then > -- cross-link a zettel bidirectionally with what's on the stash > local insert_crosslink = > function(a, rel, b_id) > if a.crosslinks == nil then > a.crosslinks = {} > end > a.crosslinks[rel] = b_id > end > insert_crosslink(curr, 'a', stash) > insert_crosslink(zettels[stash], 'a', current_zettel_id) > stash = nil > -- view settings > elseif key == string.byte('x') then > if view_settings.width > 5 then > view_settings.width = view_settings.width - 5 > end > elseif key == string.byte('X') then > if view_settings.width < w-5 then > view_settings.width = view_settings.width + 5 > end > elseif key == string.byte('y') then > if view_settings.height > 0 then > view_settings.height = view_settings.height - 1 > end > elseif key == string.byte('Y') then > if view_settings.height < h-2 then > view_settings.height = view_settings.height + 1 > end > elseif key == string.byte('z') then > -- scroll to show the current zettel at top of screen > -- often has the effect of zooming in on its hierarchy > view_settings.first_zettel = current_zettel_id > end >end - __teliva_timestamp: >Sat Feb 12 17:58:01 2022 __teliva_note: >make cursor movements less risky using a back button '<' render_state: >-- some information about what's been drawn on screen >-- not saved between app restarts >render_state = { > -- where the current zettel is, in units of zettels > curr_h = 1, > curr_w = 1, > -- what zettel is at each position on screen, in units of zettels > hw2id = {}, > -- list of zettels currently displayed > displayed = {}, > -- history of screen render state > history = {}, -- elems {first_zettel=view_settings.first_zettel, cursor=current_zettel_id} >} - __teliva_timestamp: >Thu Feb 17 20:15:14 2022 doc:blurb: >A rudimentary Zettelkasten app trying to hew very close to the original analog setup, as described by abramdemski: > >https://www.lesswrong.com/posts/NfdHG6oHBJ8Qxc26s/the-zettelkasten-method-1 > >The key attributes of Zettelkasten seem to be: >- notes organized in small fragments called 'cards' that can't hold much text >- a tree-based organization using sibling and child cards, with the ability to insert children and siblings to any card, any time >- ability to cross-link any card to any other, turning the tree into a graph (but still with a strong sense of hierarchy) > >zet.tlv satisfies these properties, but isn't very intuitive or usable yet. Contributions appreciated. - __teliva_timestamp: >Mon Mar 7 07:50:32 2022 main: >function main() > init_colors() > curses.curs_set(0) -- hide cursor except when editing > > local infile = start_reading(nil, 'zet') > if infile then > read_zettels(infile) > end > current_zettel_id = zettels.root -- cursor > view_settings.first_zettel = zettels.root -- start rendering here > > while true do > render(Window) > update(Window) > > -- save zettels, but hold on to previous state on disk > -- until last possible second > local outfile = io.open('teliva_tmp', 'w') > if outfile then > write_zettels(outfile) > local status, message = os.rename('teliva_tmp', 'zet') > assert(status, message) -- unceremoniously abort, but we hopefully only lost a little > end > -- TODO: what if io.open failed for a non-sandboxing related reason?! > -- We could silently fail to save. > end >end - __teliva_timestamp: >Mon Mar 7 07:51:06 2022 __teliva_note: >switch to new file API for reading read_zettels: >function read_zettels(infile) > zettels = jsonf.decode(infile) >end - __teliva_timestamp: >Mon Mar 7 10:31:27 2022 main: >function main() > init_colors() > curses.curs_set(0) -- hide cursor except when editing > > -- load any saved zettels > local infile = start_reading(nil, 'zet') > if infile then > read_zettels(infile) > end > current_zettel_id = zettels.root -- cursor > view_settings.first_zettel = zettels.root -- start rendering here > > while true do > render(Window) > update(Window) > > -- save zettels > local outfile = start_writing(nil, 'zet') > if outfile then > write_zettels(outfile) > end > -- TODO: what if io.open failed for a non-sandboxing related reason?! > -- We could silently fail to save. > end >end - __teliva_timestamp: >Mon Mar 7 10:32:08 2022 __teliva_note: >switch to new file API for writing write_zettels: >function write_zettels(outfile) > outfile.write(json.encode(zettels)) > outfile.close() >end - __teliva_timestamp: >Thu Mar 10 04:21:28 2022 render_zettel: >function render_zettel(window, bg, indent, edge_label, starty, startx, zettel) > window:attrset(curses.color_pair(bg)) > for y=0,view_settings.height-1 do > for x=0,view_settings.width-1 do > window:mvaddch(y+starty, x+startx, ' ') > end > end > if indent >= 2 then -- need at least 2 spaces to be able to print edge_label > window:attrset(curses.color_pair(bg+1)) -- go from zettel color to its edge color > window:mvaddstr(starty, startx+indent-1, edge_label) > window:attrset(curses.color_pair(bg)) > end > local y, x = 0, indent+1 > local data = '' > if zettel then > data = zettel.data > end > for i=1,#data do > local c = data[i] > if c == '\n' then > y = y+1 > x = indent+1 > else > window:mvaddstr(y+starty, x+startx, c) > x = x+1 > if x >= startx + view_settings.width then > y = y+1 > x = indent+1 > end > end > if y >= view_settings.height then > break > end > end >end > >function test_render_zettel_single_line_topleft() > local w = window{scr=scr{h=5, w=10}} > render_zettel(w, 34, 1, -- color 34, indent 1 > '*', 1, 1, -- startx, starty > {data='abc'}) > check_screen(w, ' abc '.. > ' '.. > ' '.. > ' '.. > ' ', > 'render_zettel: single line zettel from top-left of screen') > -- entire width is used by the single zettel > -- column 1 = margin, column 2 = indent > check_color(w, 34, '##########'.. > '##########'.. > '##########'.. > ' '.. > ' ', > 'render_zettel: single line zettel from top-left of screen, background') >end > >function test_render_zettel_from_middle_of_screen() > local w = window{scr=scr{h=5, w=10}} > render_zettel(w, 34, 1, '*', 3, 4, {data='abc'}) -- startx=3, starty=4 > check_screen(w, ' '.. > ' '.. > ' abc '.. > ' '.. > ' ', > 'render_zettel from middle of screen') > check_color(w, 34, ' '.. > ' '.. > ' #######'.. > ' #######'.. > ' #######', > 'render_zettel from middle of screen, background') >end > >function test_render_zettel_indented_prints_edge_label() > local w = window{scr=scr{h=5, w=10}} > render_zettel(w, 34, 2, '*', 3, 4, {data='abc'}) -- startx=3, starty=4 > check_screen(w, ' '.. > ' '.. > ' * abc '.. > ' '.. > ' ', > 'render_zettel: indent >= 2 prints edge label') >end > >function test_render_zettel_crops_long_lines() > local w = window{scr=scr{h=5, w=10}} > render_zettel(w, 34, 2, '*', 3, 4, {data='abc def'}) -- startx=3, starty=4 > check_screen(w, ' '.. > ' '.. > ' * abc '.. > ' '.. > ' ', > 'render_zettel: crops long lines') >end > >function test_render_zettel_multiple_lines() > local w = window{scr=scr{h=5, w=10}} > render_zettel(w, 34, 2, '*', 3, 4, {data='abc\ndef'}) -- startx=3, starty=4 > check_screen(w, ' '.. > ' '.. > ' * abc '.. > ' def '.. > ' ', > 'render_zettel: multiple lines') >end > >function test_render_zettel_truncates_extra_lines() > local w = window{scr=scr{h=5, w=10}} > render_zettel(w, 34, 2, '*', 3, 4, {data='a\nb\nc\nd'}) -- startx=3, starty=4 > check_screen(w, ' '.. > ' '.. > ' * a '.. > ' b '.. > ' c ', > 'render_zettel: truncates extra lines') >end