From 18bdd0a1e3359e8f57d0224adb6b352f92abab72 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Thu, 10 Sep 2026 19:03:29 -0500 Subject: [PATCH] More rendering fixes and Hypseus nonsense. --- CHANGELOG | 53 +- CMakeLists.txt | 47 + docs/Manual.adoc | 149 +++- src/shaders/scene.hlsl | 12 +- src/singe.c | 166 +++- testScripts/Models/Sponza.glb | 2 +- thirdparty/lpeg/LICENSE | 8 + thirdparty/lpeg/lpcap.c | 612 +++++++++++++ thirdparty/lpeg/lpcap.h | 86 ++ thirdparty/lpeg/lpcode.c | 1051 ++++++++++++++++++++++ thirdparty/lpeg/lpcode.h | 36 + thirdparty/lpeg/lpcset.c | 110 +++ thirdparty/lpeg/lpcset.h | 30 + thirdparty/lpeg/lpprint.c | 298 +++++++ thirdparty/lpeg/lpprint.h | 32 + thirdparty/lpeg/lptree.c | 1399 ++++++++++++++++++++++++++++++ thirdparty/lpeg/lptree.h | 92 ++ thirdparty/lpeg/lptypes.h | 150 ++++ thirdparty/lpeg/lpvm.c | 455 ++++++++++ thirdparty/lpeg/lpvm.h | 79 ++ thirdparty/lrandom/lrandom.c | 131 +++ thirdparty/lrandom/random.c | 128 +++ thirdparty/lua-cjson/LICENSE | 20 + thirdparty/lua-cjson/fpconv.c | 205 +++++ thirdparty/lua-cjson/fpconv.h | 22 + thirdparty/lua-cjson/lua_cjson.c | 1354 +++++++++++++++++++++++++++++ thirdparty/lua-cjson/strbuf.c | 252 ++++++ thirdparty/lua-cjson/strbuf.h | 159 ++++ thirdparty/luabitop/lbit.c | 196 +++++ thirdparty/md5/md5.c | 269 ++++++ thirdparty/md5/md5.h | 41 + thirdparty/md5/md5lib.c | 241 +++++ util/packGlb.py | 9 +- 33 files changed, 7874 insertions(+), 20 deletions(-) create mode 100644 thirdparty/lpeg/LICENSE create mode 100644 thirdparty/lpeg/lpcap.c create mode 100644 thirdparty/lpeg/lpcap.h create mode 100644 thirdparty/lpeg/lpcode.c create mode 100644 thirdparty/lpeg/lpcode.h create mode 100644 thirdparty/lpeg/lpcset.c create mode 100644 thirdparty/lpeg/lpcset.h create mode 100644 thirdparty/lpeg/lpprint.c create mode 100644 thirdparty/lpeg/lpprint.h create mode 100644 thirdparty/lpeg/lptree.c create mode 100644 thirdparty/lpeg/lptree.h create mode 100644 thirdparty/lpeg/lptypes.h create mode 100644 thirdparty/lpeg/lpvm.c create mode 100644 thirdparty/lpeg/lpvm.h create mode 100644 thirdparty/lrandom/lrandom.c create mode 100644 thirdparty/lrandom/random.c create mode 100644 thirdparty/lua-cjson/LICENSE create mode 100644 thirdparty/lua-cjson/fpconv.c create mode 100644 thirdparty/lua-cjson/fpconv.h create mode 100644 thirdparty/lua-cjson/lua_cjson.c create mode 100644 thirdparty/lua-cjson/strbuf.c create mode 100644 thirdparty/lua-cjson/strbuf.h create mode 100644 thirdparty/luabitop/lbit.c create mode 100644 thirdparty/md5/md5.c create mode 100644 thirdparty/md5/md5.h create mode 100644 thirdparty/md5/md5lib.c diff --git a/CHANGELOG b/CHANGELOG index 18233cd43..0f65d44ec 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -649,12 +649,57 @@ API Changes - navAgentNew keeps the radius and height at 0.01 or more, as navNew does, so onNavArrived fires for an agent given a radius of zero. +- Three Hypseus names the bytecode-only games turned out to need. + controllerSetPadding(boolean) and controllerGetPadding() are accepted; + the getter always answers 0, because Hypseus's offset marks a switch + code as having come from a gamepad and Singe's mapped switches carry no + such mark. videoGetVolume() with no argument answers the laserdisc's + volume on Hypseus's 0 to 64 scale, which its games double into + musicSetVolume. require("md5") gives the PUC-Rio md5 library Hypseus + bundles, all five of sum, hexsum, exor, crypt and decrypt, so a game + written against it behaves the same here; note that library names its + sumhexa hexsum. + +- The manual now says plainly that the Hypseus API is for compatibility + and not for new games, in the Lua API Reference introduction and again + at the head of the Hypseus Compatibility section. Every Hypseus name, + and every Hypseus argument form on a function that otherwise takes + Singe's own, says so in its own entry. + +- videoSetVolume(volume), with one argument instead of three, sets the + laserdisc's volume on Hypseus's 0 to 64 scale, the twin of the bare + videoGetVolume(). A level outside that range is ignored rather than + fatal, as in Hypseus. + +- drawTransparent(boolean), getUserString(), mouseGetPadding() and + spriteBlend(boolean, sprite) are registered, completing the set of + names Hypseus's engine exposes. All four are compatibility answers: + Singe carries transparency in the alpha channel rather than a colour + key, sends no statistics and so has no machine ID to report, adds no + offset to mouse input codes, and never has blending switched off. + +- allowSocketCall() and setOverlayFullAlpha() are registered so a game + that calls them does not stop on a missing function. The first always + answers false: Hypseus reports whether the user let it send data to a + stats server, and Singe sends nothing anywhere. The second is accepted + and ignored: it asks for an overlay that blends its alpha channel + rather than thresholding it, which Singe's overlay has always done. + +- The other Lua modules Hypseus exposes are here too, whether or not a + game asks for them: require("bit") is LuaBitOp, require("random") a + Mersenne Twister generator, require("cjson") Lua CJSON and + require("lpeg") LPeg. Singe already had the rest of Hypseus's list + (lfs, socket, rs232) and opens every standard Lua library besides. + - materialSetCutoff(material, cutoff) discards the texels whose base colour alpha falls below the cutoff, in the lit pass and in the shadow - pass alike, so cutout foliage casts cutout shadows. glTF's MASK alpha - mode now sets it from the file's alphaCutoff instead of warning that - the material would be drawn opaque; BLEND and OPAQUE are unchanged. - Materials that never set a cutoff keep drawing every texel. + pass alike, so cutout foliage casts cutout shadows. What survives the + cutoff is drawn fully opaque, which is what glTF means by MASK as + against BLEND: the sampled alpha is not passed on, so nothing behind + the scene shows through a leaf. glTF's MASK alpha mode now sets the + cutoff from the file's alphaCutoff instead of warning that the material + would be drawn opaque; BLEND and OPAQUE are unchanged. Materials that + never set a cutoff keep drawing every texel. - --deterministic[=MS] runs the engine on a virtual clock stepped MS milliseconds (default 15) once a frame instead of on real time, steps diff --git a/CMakeLists.txt b/CMakeLists.txt index 520fe1c15..64f621916 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -338,6 +338,48 @@ set(LUA_FILESYSTEM_SOURCE thirdparty/luafilesystem/src/lfs.h ) +# The PUC-Rio md5 library, as Hypseus bundles it (its sumhexa is named hexsum), so the games that +# require("md5") get the same five functions with the same behaviour. +set(LUA_MD5_SOURCE + thirdparty/md5/md5.c + thirdparty/md5/md5.h + thirdparty/md5/md5lib.c +) + +# The rest of the Lua modules Hypseus exposes, so a game written against it finds them here too: +# LuaBitOp, lrandom, Lua CJSON and LPeg. +set(LUA_BITOP_SOURCE + thirdparty/luabitop/lbit.c +) + +set(LUA_RANDOM_SOURCE + thirdparty/lrandom/lrandom.c +) + +set(LUA_CJSON_SOURCE + thirdparty/lua-cjson/fpconv.c + thirdparty/lua-cjson/fpconv.h + thirdparty/lua-cjson/lua_cjson.c + thirdparty/lua-cjson/strbuf.c + thirdparty/lua-cjson/strbuf.h +) + +set(LPEG_SOURCE + thirdparty/lpeg/lpcap.c + thirdparty/lpeg/lpcap.h + thirdparty/lpeg/lpcode.c + thirdparty/lpeg/lpcode.h + thirdparty/lpeg/lpcset.c + thirdparty/lpeg/lpcset.h + thirdparty/lpeg/lpprint.c + thirdparty/lpeg/lpprint.h + thirdparty/lpeg/lptree.c + thirdparty/lpeg/lptree.h + thirdparty/lpeg/lptypes.h + thirdparty/lpeg/lpvm.c + thirdparty/lpeg/lpvm.h +) + set(LUA_SOCKET_SOURCE thirdparty/luasocket/src/auxiliar.c thirdparty/luasocket/src/auxiliar.h @@ -436,6 +478,11 @@ add_executable(${CMAKE_PROJECT_NAME} ${ARG_PARSER_SOURCE} ${LUA_SOURCE} ${LUA_FILESYSTEM_SOURCE} + ${LUA_MD5_SOURCE} + ${LUA_BITOP_SOURCE} + ${LUA_RANDOM_SOURCE} + ${LUA_CJSON_SOURCE} + ${LPEG_SOURCE} ${SQLITE_SOURCE} ${LUA_SOCKET_SOURCE} ${LUASEC_SOURCE} diff --git a/docs/Manual.adoc b/docs/Manual.adoc index da619640a..0e448be8e 100644 --- a/docs/Manual.adoc +++ b/docs/Manual.adoc @@ -1981,6 +1981,9 @@ Beyond that: and chain link are drawn: one quad, most of it gone. Unlike blending it needs no sorting and it still casts a shadow, and the shadow is cut out too, so a tree throws leaf shadows rather than the shadow of its quad. + What survives the cutoff is opaque: a half-transparent texel is either + kept whole or dropped, never blended, so nothing behind the scene (the + disc picture, most visibly) tints a leaf. A cutoff of 0 (the default) turns masking off. A model loaded from glTF brings its own: `MASK` sets the cutoff from the file, `BLEND` turns blending on, and `OPAQUE` does neither. @@ -3127,10 +3130,17 @@ install any additional software: | LuaSocket | TCP and UDP | LuaRS232 | RS232 serial port access | lsqlite3 | SQLite databases, `require("sqlite3")` +| md5 | `require("md5")` for `sum`, `hexsum`, `exor`, `crypt` and `decrypt`; the PUC-Rio library Hypseus bundles, whose `sumhexa` it names `hexsum` +| LuaBitOp | `require("bit")` for the bitwise operations of Lua 5.1, which 5.4 has as operators +| lrandom | `require("random")` for a Mersenne Twister generator, separate from `math.random` +| Lua CJSON | `require("cjson")` for JSON encoding and decoding, beside the `json` module above +| LPeg | `require("lpeg")` for parsing expression grammars | RmlUi | The GUI's own object model, the `rmlui` global (see <>) |=== -Their usage is beyond the scope of this document. +Their usage is beyond the scope of this document. The last five are there +because Hypseus exposes them, so a game written against Hypseus finds the same +modules under the same names; nothing in Singe needs them. [#audiosync] === Audio Sync @@ -3247,6 +3257,20 @@ Every API entry below follows the same structure: * *See also* -- related functions you will likely use in the same call site. * *Example* -- a short, realistic snippet in the shape of real game code. +An entry that begins *Hypseus extension* is there so that a game written for +Hypseus Singe runs here unchanged. The same goes for an argument form marked as +Hypseus's on a function that otherwise takes Singe's own. + +IMPORTANT: *Do not write new games against the Hypseus API.* Those entries exist +for compatibility, not as a recommendation. Several of them answer a fixed value +because the thing they describe does not exist in Singe, some are accepted and +ignored, and others carry scales and argument orders inherited from another +engine -- `videoGetVolume()` counts to `64` where the rest of Singe counts to +`100`, and the `spriteAnim` names take the sprite last where every Singe sprite +call takes it first. Each entry names the Singe equivalent to use instead. New +code that sticks to the Singe API is shorter, consistent with itself, and will +not be surprised when a compatibility answer stays constant. + A few rules apply across the whole API: * *Colors* are 8-bit per channel: red, green, blue, and opacity are each integers from `0` to `255`. @@ -3271,6 +3295,19 @@ morph target weights are all driven. See the Animation part of <>. [#animationgettime] +==== allowSocketCall + +[source,text] +---- +allowed = allowSocketCall() +---- + +Hypseus extension, always `false` in Singe. Hypseus answers whether the user let it send data to a stats server, which its `-noserversend` turns off; the sending itself is commented out there. Singe sends nothing anywhere, so nothing is permitted and the answer never changes. It exists so a game that asks does not stop on a missing function. + +*Returns:* a boolean, always `false`. + +*Since:* 3.00 + ==== animationGetTime [source,text] @@ -4706,6 +4743,20 @@ end ---- [#controllerhowmany] +==== controllerGetPadding + +[source,text] +---- +padding = controllerGetPadding() +---- + +Hypseus extension, always `0` in Singe. Hypseus adds this offset to a switch code that came from a gamepad, so a game can tell which device pressed what and still recover the switch by subtracting it. Singe delivers gamepad input as ordinary mapped switches carrying no such mark, so it answers `0`: a game testing for the padded range takes its other branch and reads the switch as it stands. + +*Returns:* an integer, always `0`. + +*Since:* 3.00 +*See also:* <> + ==== controllerHowMany [source,text] @@ -4769,6 +4820,20 @@ end Two calls for seeing what a script is doing: `debugPrint` writes text, and `lineDraw` draws a line over the 3D scene for one frame (`physicsSetDebug` and `navDraw` draw through the same lines). Printed output goes to the console Singe was started from, so it is visible during development and harmless in a cabinet, where `--noconsole` silences it. When Singe runs with `--trace`, every API call including this one is also written with its arguments to `trace.txt` in the data directory, which is the way to get a permanent record. Command line options are listed in the chapter on running Singe; the ZeroBrane Studio integration in `zbstudio/` offers a debugger when printing is not enough. [[traceheader]] +==== controllerSetPadding + +[source,text] +---- +controllerSetPadding(boolean) +---- + +Hypseus extension, accepted and ignored. See <> for why the offset it asks for does not exist in Singe. Turning it on without marking the events would be worse than leaving it off, because the game would then subtract an offset from switch codes that never carried one. + +*Returns:* nothing. + +*Since:* 3.00 +*See also:* <> + ==== The Trace Header Whichever of `--program` and `--trace` opened it, `trace.txt` begins with a @@ -4871,6 +4936,19 @@ end The *disc* is the main laserdisc video, the one Singe was originally built to emulate. A game has at most one disc, opened at startup from the `VIDEO` named in `games.dat` or from `--disc` / `--framefile` on the command line; a game that names none runs without a disc (see <>). All `disc*` functions operate on this single implicit disc and take no handle. Frame numbers are zero based positions in the video file (or in the combined framefile) and are the time reference for every branch decision in a laserdisc game. Without a disc the query functions return `0`, `discGetState` reports `DISC_EJECTED`, and the transport functions trace and do nothing. Extra clips layered on top of the disc are the job of the <> family. [#discaudio] +==== drawTransparent + +[source,text] +---- +drawTransparent(boolean) +---- + +Hypseus extension, accepted and ignored. It decides whether Hypseus sets colour key 0 on the sprites loaded after it, which is how that engine carries transparency. Singe carries transparency in the alpha channel, as <> already describes, so there is no key to turn on or off. + +*Returns:* nothing. + +*Since:* 3.00 + ==== discAudio [source,text] @@ -6670,6 +6748,20 @@ end ---- [#guigetheight] +==== getUserString + +[source,text] +---- +id = getUserString() +---- + +Hypseus extension, always an empty string. Despite the name it is not a string the user set: Hypseus answers the machine install ID it sends with network statistics, taken from the Windows `MachineGuid` or the Linux machine ID. Singe sends nothing anywhere and will not hand a game a fingerprint of the machine it is running on. + +*Returns:* a string, always empty. + +*Since:* 3.00 +*See also:* <> + ==== guiGetHeight [source,text] @@ -8796,6 +8888,20 @@ end ---- [#mousegetposition] +==== mouseGetPadding + +[source,text] +---- +padding = mouseGetPadding() +---- + +Hypseus extension, always `0`, for the reason <> gives. Hypseus adds this offset to the input codes its mice report in relative mode; Singe's codes carry no such offset, so there is nothing to subtract. + +*Returns:* an integer, always `0`. + +*Since:* 3.00 +*See also:* <> + ==== mouseGetPosition [source,text] @@ -11136,6 +11242,19 @@ CABINET_H = overlayGetHeight() - MARGIN_Y - CABINET_Y ---- [#setoverlaylinearscale] +==== setOverlayFullAlpha + +[source,text] +---- +setOverlayFullAlpha() +---- + +Hypseus extension, accepted and ignored. Hypseus blits its overlay with the alpha channel thresholded at `128`, so a half-transparent pixel is either fully opaque or gone, and this asks it to upload the surface as it stands and blend properly instead. Singe's overlay has always been full RGBA with real per-pixel alpha, so what this asks for is already in force. + +*Returns:* nothing. + +*Since:* 3.00 + ==== setOverlayLinearScale [source,text] @@ -14158,6 +14277,19 @@ Sprites are 2D bitmaps loaded from disk with `spriteLoad`, or rendered from text *Sprite sheets.* Hypseus Singe extends the family with sprite sheets: `spriteLoadFrames` slices a single row strip of equal frames into an animation that is not playing, and `spriteDrawFrame` draws one chosen frame (1-based) without touching playback. A sheet is otherwise an ordinary animation, so `spriteSetFrame` (0-based), `spritePlay`, `spriteDraw`, `nodeSetSprite` and `emitterSetTexture` all accept it, and `spriteFrameWidth` and `spriteFrameHeight` report the unscaled size of one frame. The entries marked "Hypseus extension" exist so games written for Hypseus Singe run unchanged. [#spritedraw] +==== spriteBlend + +[source,text] +---- +spriteBlend(boolean, sprite) +---- + +Hypseus extension, accepted and ignored. It is a workaround for that engine: Hypseus loads sprites with SDL's blend mode off, and this turns it back on for one sprite. Singe draws every sprite with its alpha channel already, so blending is never off to begin with. The sprite handle is still checked, so a bad one is still an error. + +*Returns:* nothing. + +*Since:* 3.00 + ==== spriteDraw [source,text] @@ -16042,13 +16174,16 @@ end [source,text] ---- left, right = videoGetVolume(id) +volume = videoGetVolume() ---- Returns the stereo volume of a loaded video as two percentages. A freshly loaded video is at the `--volume_nonvldp` level on both channels (default `100`). Note that two values come back, left first. -*Returns:* two integers, left and right, each `0` to `100`. +Called with no argument it is a Hypseus extension answering the laserdisc's own volume instead, as a single number on Hypseus's `0` to `64` scale rather than as a percentage. Its games double that for <>, whose range is `0` to `128`. The level follows `--volume_vldp`. -*Since:* 2.00 +*Returns:* two integers, left and right, each `0` to `100`; or with no argument one integer, `0` to `64`. + +*Since:* 2.00, the no-argument disc form 3.00 *See also:* <>, <> .Example @@ -16395,10 +16530,13 @@ videoPlay(attractVideo) [source,text] ---- videoSetVolume(id, left, right) +videoSetVolume(volume) ---- Sets the stereo volume of a loaded video as percentages. Values are clamped to `0` through `100` without complaint. The setting is remembered even for a silent clip and is reported back by `videoGetVolume`. +Called with one argument it is a Hypseus extension setting the laserdisc's own volume instead, on Hypseus's `0` to `64` scale rather than as a percentage: the twin of the bare <>. A level outside `0` to `64` is ignored rather than fatal, which is what Hypseus does with one. + *Parameters:* * `id` -- video handle. @@ -17264,6 +17402,11 @@ end [#unimplementedandlegacy] === Hypseus Compatibility +IMPORTANT: Everything in this section exists so that a game written for Hypseus +Singe runs unchanged. *None of it is recommended for a new game.* Prefer the +Singe call each entry names: it is the one that will keep gaining features, and +it does not carry another engine's scales, argument orders or fixed answers. + Hypseus Singe registers a few names in its engine that Singe answers from `Singe/Framework.singe` instead, so that a game written for Hypseus runs unchanged. Each one is defined only when the engine does not already provide diff --git a/src/shaders/scene.hlsl b/src/shaders/scene.hlsl index 1e0f55456..d6ae4c720 100644 --- a/src/shaders/scene.hlsl +++ b/src/shaders/scene.hlsl @@ -504,10 +504,16 @@ float4 fragmentMain(VertexOutput input) : SV_Target { } else if (material.w > 0.5) { albedo *= baseTexture.Sample(baseSampler, uv); } - // Alpha masking: a glTF MASK material keeps only the texels at or above its cutoff. Tested + // Alpha masking: a glTF MASK material keeps only the texels at or above its cutoff, and what + // it keeps is fully opaque -- that is what MASK means, as against BLEND. Passing the sampled + // alpha on instead would let whatever sits behind the scene show through a leaf in proportion + // to it, which with a transparent background is the disc picture, tinting the foliage. Tested // before any lighting, so a discarded texel costs nothing beyond the sample above. - if ((maps.z > 0.0) && (albedo.a < maps.z)) { - discard; + if (maps.z > 0.0) { + if (albedo.a < maps.z) { + discard; + } + albedo.a = 1.0; } if (material.z > 0.5) { return float4(applyFog(albedo.rgb, input.worldPosition), albedo.a); diff --git a/src/singe.c b/src/singe.c index 06c86c591..a5d3e34de 100644 --- a/src/singe.c +++ b/src/singe.c @@ -50,6 +50,11 @@ LUASOCKET_API int luaopen_socket_serial(lua_State *L); int luaopen_luars232(lua_State *L); // Nor for lsqlite3. int luaopen_lsqlite3(lua_State *L); +int luaopen_md5_core(lua_State *L); +int luaopen_bit(lua_State *L); +int luaopen_random(lua_State *L); +int luaopen_cjson(lua_State *L); +int luaopen_lpeg(lua_State *L); // There is no header for ssl.config binding. Make our own. LSEC_API int luaopen_ssl_config(lua_State *L); @@ -88,6 +93,7 @@ LSEC_API int luaopen_ssl_config(lua_State *L); #define CONTROLLER_AXIS_COUNT 6 #define CONTROLLER_BUTTON_COUNT 15 #define CONTROLLER_DEAD_ZONE_DEFAULT 15000 +#define HYPSEUS_VOLUME_MAX 64 // What Hypseus's videoGetVolume() answers at full volume #define CONTROLLER_TRIGGER_FIRST 4 // Gamepad axes 4 and 5 are the two analogue triggers #define AXIS_VALUE_MAX 32767 // What SDL reports at full deflection, and the unit DEAD_ZONE counts in #define PERCENT_FULL 100.0 // TRIGGER_THRESHOLD is a per cent of that full deflection, as Hypseus writes it @@ -863,6 +869,7 @@ static void _videoDestroy(VideoT *video); static bool _watchedChanged(void); static void _watchFile(const char *name); +static int32_t apiAllowSocketCall(lua_State *L); static int32_t apiAnimationGetTime(lua_State *L); static int32_t apiAnimationIsPlaying(lua_State *L); static int32_t apiAnimationPause(lua_State *L); @@ -899,8 +906,10 @@ static int32_t apiControllerDoRumble(lua_State *L); static int32_t apiControllerGetAxis(lua_State *L); static int32_t apiControllerGetButton(lua_State *L); static int32_t apiControllerGetName(lua_State *L); +static int32_t apiControllerGetPadding(lua_State *L); static int32_t apiControllerHowMany(lua_State *L); static int32_t apiControllerIsValid(lua_State *L); +static int32_t apiControllerSetPadding(lua_State *L); static int32_t apiDebugPrint(lua_State *L); static int32_t apiDiscAudio(lua_State *L); static int32_t apiDiscAudioSuffix(lua_State *L); @@ -925,6 +934,7 @@ static int32_t apiDiscSkipToFrame(lua_State *L); static int32_t apiDiscStepBackward(lua_State *L); static int32_t apiDiscStepForward(lua_State *L); static int32_t apiDiscStop(lua_State *L); +static int32_t apiDrawTransparent(lua_State *L); static int32_t apiEmitterBurst(lua_State *L); static int32_t apiEmitterClear(lua_State *L); static int32_t apiEmitterDelete(lua_State *L); @@ -963,6 +973,7 @@ static int32_t apiFontSelect(lua_State *L); static int32_t apiFontToSprite(lua_State *L); static int32_t apiFontUnload(lua_State *L); static int32_t apiGetFValue(lua_State *L); +static int32_t apiGetUserString(lua_State *L); static int32_t apiGuiClose(lua_State *L); static int32_t apiGuiDelete(lua_State *L); static int32_t apiGuiDraw(lua_State *L); @@ -1034,6 +1045,7 @@ static int32_t apiModelDelete(lua_State *L); static int32_t apiModelGetAnimations(lua_State *L); static int32_t apiModelInstance(lua_State *L); static int32_t apiModelLoad(lua_State *L); +static int32_t apiMouseGetPadding(lua_State *L); static int32_t apiMusicIsPlaying(lua_State *L); static int32_t apiMusicLoad(lua_State *L); static int32_t apiMusicPause(lua_State *L); @@ -1171,6 +1183,7 @@ static int32_t apiScoreBezelScore(lua_State *L); static int32_t apiScoreBezelTwinScoreOn(lua_State *L); static int32_t apiScriptExecute(lua_State *L); static int32_t apiScriptPush(lua_State *L); +static int32_t apiSetOverlayFullAlpha(lua_State *L); static int32_t apiSetOverlayLinearScale(lua_State *L); static int32_t apiSetOverlayOnTop(lua_State *L); static int32_t apiSetOverlayOpacity(lua_State *L); @@ -1219,6 +1232,7 @@ static int32_t apiSoundSetRange(lua_State *L); static int32_t apiSoundSetVolume(lua_State *L); static int32_t apiSoundStop(lua_State *L); static int32_t apiSoundUnload(lua_State *L); +static int32_t apiSpriteBlend(lua_State *L); static int32_t apiSpriteDraw(lua_State *L); static int32_t apiSpriteDrawFrame(lua_State *L); static int32_t apiSpriteDrawGrid(lua_State *L); @@ -1315,6 +1329,13 @@ static int32_t apiVldpSetVerbose(lua_State *L); static const LuaModuleT _luaModules[] = { // LuaFileSystem MODC("lfs", _luaopenLfs), + // md5, the PUC-Rio library Hypseus bundles: sum, hexsum, exor, crypt, decrypt + MODC("md5", luaopen_md5_core), + // The rest of what Hypseus exposes, so a game written against it finds the same modules here + MODC("bit", luaopen_bit), + MODC("random", luaopen_random), + MODC("cjson", luaopen_cjson), + MODC("lpeg", luaopen_lpeg), // SQLite for script data MODC("sqlite3", luaopen_lsqlite3), // LuaSocket @@ -4853,6 +4874,7 @@ static void _readColor(lua_State *L, const char *method, SDL_Color *color, uint8 // Every Singe call a script may make. Comments give the version each call appeared in. static void _registerApi(lua_State *L) { + lua_register(L, "allowSocketCall", apiAllowSocketCall); // Hypseus lua_register(L, "animationGetTime", apiAnimationGetTime); // 3.00 lua_register(L, "animationIsPlaying", apiAnimationIsPlaying); // 3.00 lua_register(L, "animationPause", apiAnimationPause); // 3.00 @@ -4889,8 +4911,10 @@ static void _registerApi(lua_State *L) { lua_register(L, "controllerGetAxis", apiControllerGetAxis); // 2.00 lua_register(L, "controllerGetButton", apiControllerGetButton); // 2.10 lua_register(L, "controllerGetName", apiControllerGetName); // 3.00 + lua_register(L, "controllerGetPadding", apiControllerGetPadding); // Hypseus lua_register(L, "controllerHowMany", apiControllerHowMany); // Hypseus lua_register(L, "controllerIsValid", apiControllerIsValid); // Hypseus + lua_register(L, "controllerSetPadding", apiControllerSetPadding); // Hypseus lua_register(L, "debugPrint", apiDebugPrint); // 1.xx @@ -4919,6 +4943,7 @@ static void _registerApi(lua_State *L) { lua_register(L, "discStepForward", apiDiscStepForward); // 1.xx lua_register(L, "discStop", apiDiscStop); // 1.xx + lua_register(L, "drawTransparent", apiDrawTransparent); // Hypseus lua_register(L, "emitterBurst", apiEmitterBurst); // 3.00 lua_register(L, "emitterClear", apiEmitterClear); // 3.00 lua_register(L, "emitterDelete", apiEmitterDelete); // 3.00 @@ -4957,6 +4982,7 @@ static void _registerApi(lua_State *L) { lua_register(L, "fontToSprite", apiFontToSprite); // 1.xx lua_register(L, "fontUnload", apiFontUnload); // 2.00 lua_register(L, "getFValue", apiGetFValue); // Hypseus + lua_register(L, "getUserString", apiGetUserString); // Hypseus lua_register(L, "guiClose", apiGuiClose); // 3.00 lua_register(L, "guiDelete", apiGuiDelete); // 3.00 lua_register(L, "guiDraw", apiGuiDraw); // 3.00 @@ -5030,6 +5056,7 @@ static void _registerApi(lua_State *L) { lua_register(L, "modelInstance", apiModelInstance); // 3.00 lua_register(L, "modelLoad", apiModelLoad); // 3.00 lua_register(L, "mouseGetName", apiMouseGetName); // 3.00 + lua_register(L, "mouseGetPadding", apiMouseGetPadding); // Hypseus lua_register(L, "mouseGetPosition", apiMouseGetPosition); // 2.00 lua_register(L, "mouseHowMany", apiMouseHowMany); // 1.18 RDG lua_register(L, "mouseSetCaptured", apiMouseSetCaptured); // 2.00 @@ -5168,6 +5195,7 @@ static void _registerApi(lua_State *L) { lua_register(L, "scoreBezelTwinScoreOn", apiScoreBezelTwinScoreOn); // Hypseus lua_register(L, "scriptExecute", apiScriptExecute); // 2.00 lua_register(L, "scriptPush", apiScriptPush); // 2.00 + lua_register(L, "setOverlayFullAlpha", apiSetOverlayFullAlpha); // Hypseus lua_register(L, "setOverlayLinearScale", apiSetOverlayLinearScale); // Hypseus lua_register(L, "setOverlayOnTop", apiSetOverlayOnTop); // Hypseus lua_register(L, "setOverlayOpacity", apiSetOverlayOpacity); // Hypseus @@ -5219,6 +5247,7 @@ static void _registerApi(lua_State *L) { lua_register(L, "soundStop", apiSoundStop); // 1.xx RDG lua_register(L, "soundUnload", apiSoundUnload); // 2.00 + lua_register(L, "spriteBlend", apiSpriteBlend); // Hypseus lua_register(L, "spriteDraw", apiSpriteDraw); // 1.xx Handle first since 3.00. lua_register(L, "spriteDrawFrame", apiSpriteDrawFrame); // Hypseus lua_register(L, "spriteDrawGrid", apiSpriteDrawGrid); // Hypseus @@ -6943,6 +6972,19 @@ static void _watchFile(const char *name) { // seconds = animationGetTime(node) +// allowSocketCall() +// Hypseus answers whether the user let it send data to a stats server, which -noserversend turns +// off; the sending itself is commented out there. Singe sends nothing anywhere, so the answer is +// false and stays false. It exists so a game that asks does not die on a nil call. +static int32_t apiAllowSocketCall(lua_State *L) { + _argCheck(L, "allowSocketCall", 0, 0); + lua_pushboolean(L, false); + _luaTrace(L, "allowSocketCall", "false"); + + return 1; +} + + static int32_t apiAnimationGetTime(lua_State *L) { int32_t root; @@ -7474,6 +7516,19 @@ static int32_t apiControllerGetName(lua_State *L) { // count = controllerHowMany() Hypseus extension. Gamepads open in the four slots. +// controllerGetPadding() +// Hypseus adds this offset to a switch code that came from a gamepad, so a game can tell which +// device pressed what and still recover the switch by subtracting it. Singe delivers gamepad input +// as ordinary mapped switches with no such mark, so the honest answer is zero: a game then takes +// its "not a gamepad" branch and works, rather than subtracting a number that means nothing here. +static int32_t apiControllerGetPadding(lua_State *L) { + _argCheck(L, "controllerGetPadding", 0, 0); + lua_pushinteger(L, 0); + _luaTrace(L, "controllerGetPadding", "0"); + return 1; +} + + static int32_t apiControllerHowMany(lua_State *L) { int32_t count = 0; int32_t x = 0; @@ -7509,6 +7564,17 @@ static int32_t apiControllerIsValid(lua_State *L) { } +// controllerSetPadding(boolean) +// Accepted and ignored, as controllerGetPadding explains. Turning the offset on without marking +// the events would be worse than leaving it off: the game would subtract it from switch codes that +// never carried it. +static int32_t apiControllerSetPadding(lua_State *L) { + _argCheck(L, "controllerSetPadding", 1, 1); + _argBoolean(L, "controllerSetPadding", 1); + return 0; +} + + // debugPrint(message) static int32_t apiDebugPrint(lua_State *L) { const char *message = NULL; @@ -7523,6 +7589,18 @@ static int32_t apiDebugPrint(lua_State *L) { // discAudio(channel, enabled) Channel 1 is left, 2 is right. +// drawTransparent(boolean) +// Accepted and ignored. It decides whether Hypseus sets colour key 0 on the sprites loaded after +// it, which is how that engine carries transparency. Singe carries it in the alpha channel, as +// spriteResetColorKey already documents, so there is no key to turn on or off. +static int32_t apiDrawTransparent(lua_State *L) { + _argCheck(L, "drawTransparent", 1, 1); + _argBoolean(L, "drawTransparent", 1); + + return 0; +} + + static int32_t apiDiscAudio(lua_State *L) { int32_t channel = 0; int32_t left = 0; @@ -8336,6 +8414,19 @@ static int32_t apiGuiDrawScreen(lua_State *L) { } +// getUserString() +// Always empty. Despite the name it is not a user string: Hypseus answers the machine install ID +// it sends with network statistics, from the Windows MachineGuid or the Linux machine-id. Singe +// sends nothing anywhere and will not hand a game a fingerprint of the machine it runs on. +static int32_t apiGetUserString(lua_State *L) { + _argCheck(L, "getUserString", 0, 0); + lua_pushstring(L, ""); + _luaTrace(L, "getUserString", "empty"); + + return 1; +} + + static int32_t apiGuiGetHeight(lua_State *L) { _argCheck(L, "guiGetHeight", 1, 1); lua_pushinteger(L, guiGetHeight(_argGui(L, "guiGetHeight", 1))); @@ -8650,7 +8741,6 @@ static int32_t apiKeyboardGetLastUp(lua_State *L) { } -// mode = keyboardGetMode() static int32_t apiKeyboardGetMode(lua_State *L) { _luaTrace(L, "keyboardGetMode", "%d", _global.keyboardMode); lua_pushinteger(L, _global.keyboardMode); @@ -9581,6 +9671,18 @@ static int32_t apiMouseGetName(lua_State *L) { // x, y = mouseGetPosition(mouse) +// mouseGetPadding() +// Always 0, for the reason controllerGetPadding gives: Hypseus adds this offset to the input codes +// its mice report in relative mode, and Singe's codes carry no such offset to subtract. +static int32_t apiMouseGetPadding(lua_State *L) { + _argCheck(L, "mouseGetPadding", 0, 0); + lua_pushinteger(L, 0); + _luaTrace(L, "mouseGetPadding", "0"); + + return 1; +} + + static int32_t apiMouseGetPosition(lua_State *L) { int32_t m = 0; int32_t x = 0; @@ -11006,7 +11108,6 @@ static int32_t apiRatioGetY(lua_State *L) { } -// sceneEnable(bool): turns the 3D layer on or off static int32_t apiSceneEnable(lua_State *L) { bool enabled; @@ -11363,7 +11464,13 @@ static int32_t apiScriptPush(lua_State *L) { } -// setOverlayLinearScale(enabled) Hypseus extension. Smooths the overlay as it is scaled to the window instead of blocking it up. +static int32_t apiSetOverlayFullAlpha(lua_State *L) { + _argCheck(L, "setOverlayFullAlpha", 0, 0); + + return 0; +} + + static int32_t apiSetOverlayLinearScale(lua_State *L) { bool enabled = false; @@ -12010,6 +12117,18 @@ static int32_t apiSoundUnload(lua_State *L) { // spriteDraw(id, x, y[, centered]) - Draw at natural size // spriteDraw(id, x, y, x2, y2[, centered]) - Stretch into the rectangle +// spriteBlend(boolean, id) +// Accepted and ignored. It is a Hypseus workaround: that engine loads sprites with SDL's blend +// mode off, and this turns it back on per sprite. Singe draws every sprite with its alpha already. +static int32_t apiSpriteBlend(lua_State *L) { + _argCheck(L, "spriteBlend", 2, 2); + _argBoolean(L, "spriteBlend", 1); + _argSprite(L, "spriteBlend", 2); + + return 0; +} + + static int32_t apiSpriteDraw(lua_State *L) { int32_t n = lua_gettop(L); bool center = false; @@ -12294,7 +12413,6 @@ static int32_t apiSpriteIsPlaying(lua_State *L) { } -// id = spriteLoad(filename) Animated GIF and WEBP files load as animations. static int32_t apiSpriteLoad(lua_State *L) { const char *name = NULL; SpriteT *sprite = NULL; @@ -13073,13 +13191,25 @@ static int32_t apiVideoGetLanguageDescription(lua_State *L) { } -// left, right = videoGetVolume(id) +// left, right = videoGetVolume(id), or volume = videoGetVolume() for the laserdisc static int32_t apiVideoGetVolume(lua_State *L) { VideoT *video = NULL; int32_t left = 0; int32_t right = 0; - _argCheck(L, "videoGetVolume", 1, 1); + _argCheck(L, "videoGetVolume", 0, 1); + // Hypseus extension: no argument answers the laserdisc's own volume, on the 0 to 64 scale its + // games expect -- they double it for musicSetVolume, whose range is 0 to 128, and a level + // outside that ends the script. Singe keeps the disc volume as a percentage, so it is scaled + // here rather than kept twice. + if (lua_gettop(L) == 0) { + int32_t level = (_global.conf->volumeVldp * HYPSEUS_VOLUME_MAX) / VOLUME_MAX; + + _luaTrace(L, "videoGetVolume", "disc %d", level); + lua_pushinteger(L, level); + + return 1; + } video = _argVideo(L, "videoGetVolume", 1); videoGetVolume(video->handle, &left, &right); _luaTrace(L, "videoGetVolume", "%d %d %d", video->id, left, right); @@ -13301,7 +13431,29 @@ static int32_t apiVideoSetVolume(lua_State *L) { int32_t left = 0; int32_t right = 0; - _argCheck(L, "videoSetVolume", 3, 3); + _argCheck(L, "videoSetVolume", 1, 3); + // Hypseus extension: one argument sets the laserdisc's own volume on its 0 to 64 scale, the + // twin of the bare videoGetVolume(). Hypseus ignores a level outside that range with a warning + // rather than stopping the script, and so does this: a game that oversteps keeps playing. + if (lua_gettop(L) == 1) { + int32_t level = _argInteger(L, "videoSetVolume", 1); + + if ((level < 0) || (level > HYPSEUS_VOLUME_MAX)) { + _luaTrace(L, "videoSetVolume", "disc %d out of range, ignored", level); + + return 0; + } + _global.conf->volumeVldp = (level * VOLUME_MAX) / HYPSEUS_VOLUME_MAX; + if (_global.videoHandle >= 0) { + videoSetVolume(_global.videoHandle, _global.conf->volumeVldp, _global.conf->volumeVldp); + } + _luaTrace(L, "videoSetVolume", "disc %d (%d percent)", level, _global.conf->volumeVldp); + + return 0; + } + if (lua_gettop(L) != 3) { + _luaDie(L, "videoSetVolume", "Expected 1 or 3 arguments, got %d.", lua_gettop(L)); + } video = _argVideo(L, "videoSetVolume", 1); left = _argInteger(L, "videoSetVolume", 2); right = _argInteger(L, "videoSetVolume", 3); diff --git a/testScripts/Models/Sponza.glb b/testScripts/Models/Sponza.glb index 666d33dfd..6a6858a06 100644 --- a/testScripts/Models/Sponza.glb +++ b/testScripts/Models/Sponza.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5be7c78aced3b8c042813bc554225e240ea0136374ff2e900adb6caaa350c8fe +oid sha256:809912c262b36d87b34b9e9ab604c50399b01ed346c2c2974803b2c0d34c121a size 17619220 diff --git a/thirdparty/lpeg/LICENSE b/thirdparty/lpeg/LICENSE new file mode 100644 index 000000000..d4ee333c1 --- /dev/null +++ b/thirdparty/lpeg/LICENSE @@ -0,0 +1,8 @@ +License +Copyright © 2007-2023 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. diff --git a/thirdparty/lpeg/lpcap.c b/thirdparty/lpeg/lpcap.c new file mode 100644 index 000000000..f13ecf4d8 --- /dev/null +++ b/thirdparty/lpeg/lpcap.c @@ -0,0 +1,612 @@ + +#include "lua.h" +#include "lauxlib.h" + +#include "lpcap.h" +#include "lpprint.h" +#include "lptypes.h" + + +#define getfromktable(cs,v) lua_rawgeti((cs)->L, ktableidx((cs)->ptop), v) + +#define pushluaval(cs) getfromktable(cs, (cs)->cap->idx) + + + +#define skipclose(cs,head) \ + if (isopencap(head)) { assert(isclosecap(cs->cap)); cs->cap++; } + + +/* +** Return the size of capture 'cap'. If it is an open capture, 'close' +** must be its corresponding close. +*/ +static Index_t capsize (Capture *cap, Capture *close) { + if (isopencap(cap)) { + assert(isclosecap(close)); + return close->index - cap->index; + } + else + return cap->siz - 1; +} + + +static Index_t closesize (CapState *cs, Capture *head) { + return capsize(head, cs->cap); +} + + +/* +** Put at the cache for Lua values the value indexed by 'v' in ktable +** of the running pattern (if it is not there yet); returns its index. +*/ +static int updatecache (CapState *cs, int v) { + int idx = cs->ptop + 1; /* stack index of cache for Lua values */ + if (v != cs->valuecached) { /* not there? */ + getfromktable(cs, v); /* get value from 'ktable' */ + lua_replace(cs->L, idx); /* put it at reserved stack position */ + cs->valuecached = v; /* keep track of what is there */ + } + return idx; +} + + +static int pushcapture (CapState *cs); + + +/* +** Goes back in a list of captures looking for an open capture +** corresponding to a close one. +*/ +static Capture *findopen (Capture *cap) { + int n = 0; /* number of closes waiting an open */ + for (;;) { + cap--; + if (isclosecap(cap)) n++; /* one more open to skip */ + else if (isopencap(cap)) + if (n-- == 0) return cap; + } +} + + +/* +** Go to the next capture at the same level. +*/ +static void nextcap (CapState *cs) { + Capture *cap = cs->cap; + if (isopencap(cap)) { /* must look for a close? */ + int n = 0; /* number of opens waiting a close */ + for (;;) { /* look for corresponding close */ + cap++; + if (isopencap(cap)) n++; + else if (isclosecap(cap)) + if (n-- == 0) break; + } + cs->cap = cap + 1; /* + 1 to skip last close */ + } + else { + Capture *next; + for (next = cap + 1; capinside(cap, next); next++) + ; /* skip captures inside current one */ + cs->cap = next; + } +} + + +/* +** Push on the Lua stack all values generated by nested captures inside +** the current capture. Returns number of values pushed. 'addextra' +** makes it push the entire match after all captured values. The +** entire match is pushed also if there are no other nested values, +** so the function never returns zero. +*/ +static int pushnestedvalues (CapState *cs, int addextra) { + Capture *head = cs->cap++; /* original capture */ + int n = 0; /* number of pushed subvalues */ + /* repeat for all nested patterns */ + while (capinside(head, cs->cap)) + n += pushcapture(cs); + if (addextra || n == 0) { /* need extra? */ + lua_pushlstring(cs->L, cs->s + head->index, closesize(cs, head)); + n++; + } + skipclose(cs, head); + return n; +} + + +/* +** Push only the first value generated by nested captures +*/ +static void pushonenestedvalue (CapState *cs) { + int n = pushnestedvalues(cs, 0); + if (n > 1) + lua_pop(cs->L, n - 1); /* pop extra values */ +} + + +/* +** Checks whether group 'grp' is visible to 'ref', that is, 'grp' is +** not nested inside a full capture that does not contain 'ref'. (We +** only need to care for full captures because the search at 'findback' +** skips open-end blocks; so, if 'grp' is nested in a non-full capture, +** 'ref' is also inside it.) To check this, we search backward for the +** inner full capture enclosing 'grp'. A full capture cannot contain +** non-full captures, so a close capture means we cannot be inside a +** full capture anymore. +*/ +static int capvisible (CapState *cs, Capture *grp, Capture *ref) { + Capture *cap = grp; + int i = MAXLOP; /* maximum distance for an 'open' */ + while (i-- > 0 && cap-- > cs->ocap) { + if (isclosecap(cap)) + return 1; /* can stop the search */ + else if (grp->index - cap->index >= UCHAR_MAX) + return 1; /* can stop the search */ + else if (capinside(cap, grp)) /* is 'grp' inside cap? */ + return capinside(cap, ref); /* ok iff cap also contains 'ref' */ + } + return 1; /* 'grp' is not inside any capture */ +} + + +/* +** Try to find a named group capture with the name given at the top of +** the stack; goes backward from 'ref'. +*/ +static Capture *findback (CapState *cs, Capture *ref) { + lua_State *L = cs->L; + Capture *cap = ref; + while (cap-- > cs->ocap) { /* repeat until end of list */ + if (isclosecap(cap)) + cap = findopen(cap); /* skip nested captures */ + else if (capinside(cap, ref)) + continue; /* enclosing captures are not visible to 'ref' */ + if (captype(cap) == Cgroup && capvisible(cs, cap, ref)) { + getfromktable(cs, cap->idx); /* get group name */ + if (lp_equal(L, -2, -1)) { /* right group? */ + lua_pop(L, 2); /* remove reference name and group name */ + return cap; + } + else lua_pop(L, 1); /* remove group name */ + } + } + luaL_error(L, "back reference '%s' not found", lua_tostring(L, -1)); + return NULL; /* to avoid warnings */ +} + + +/* +** Back-reference capture. Return number of values pushed. +*/ +static int backrefcap (CapState *cs) { + int n; + Capture *curr = cs->cap; + pushluaval(cs); /* reference name */ + cs->cap = findback(cs, curr); /* find corresponding group */ + n = pushnestedvalues(cs, 0); /* push group's values */ + cs->cap = curr + 1; + return n; +} + + +/* +** Table capture: creates a new table and populates it with nested +** captures. +*/ +static int tablecap (CapState *cs) { + lua_State *L = cs->L; + Capture *head = cs->cap++; + int n = 0; + lua_newtable(L); + while (capinside(head, cs->cap)) { + if (captype(cs->cap) == Cgroup && cs->cap->idx != 0) { /* named group? */ + pushluaval(cs); /* push group name */ + pushonenestedvalue(cs); + lua_settable(L, -3); + } + else { /* not a named group */ + int i; + int k = pushcapture(cs); + for (i = k; i > 0; i--) /* store all values into table */ + lua_rawseti(L, -(i + 1), n + i); + n += k; + } + } + skipclose(cs, head); + return 1; /* number of values pushed (only the table) */ +} + + +/* +** Table-query capture +*/ +static int querycap (CapState *cs) { + int idx = cs->cap->idx; + pushonenestedvalue(cs); /* get nested capture */ + lua_gettable(cs->L, updatecache(cs, idx)); /* query cap. value at table */ + if (!lua_isnil(cs->L, -1)) + return 1; + else { /* no value */ + lua_pop(cs->L, 1); /* remove nil */ + return 0; + } +} + + +/* +** Fold capture +*/ +static int foldcap (CapState *cs) { + int n; + lua_State *L = cs->L; + Capture *head = cs->cap++; + int idx = head->idx; + if (isclosecap(cs->cap) || /* no nested captures (large subject)? */ + (n = pushcapture(cs)) == 0) /* nested captures with no values? */ + return luaL_error(L, "no initial value for fold capture"); + if (n > 1) + lua_pop(L, n - 1); /* leave only one result for accumulator */ + while (capinside(head, cs->cap)) { + lua_pushvalue(L, updatecache(cs, idx)); /* get folding function */ + lua_insert(L, -2); /* put it before accumulator */ + n = pushcapture(cs); /* get next capture's values */ + lua_call(L, n + 1, 1); /* call folding function */ + } + skipclose(cs, head); + return 1; /* only accumulator left on the stack */ +} + + +/* +** Function capture +*/ +static int functioncap (CapState *cs) { + int n; + int top = lua_gettop(cs->L); + pushluaval(cs); /* push function */ + n = pushnestedvalues(cs, 0); /* push nested captures */ + lua_call(cs->L, n, LUA_MULTRET); /* call function */ + return lua_gettop(cs->L) - top; /* return function's results */ +} + + +/* +** Accumulator capture +*/ +static int accumulatorcap (CapState *cs) { + lua_State *L = cs->L; + int n; + if (lua_gettop(L) < cs->firstcap) + luaL_error(L, "no previous value for accumulator capture"); + pushluaval(cs); /* push function */ + lua_insert(L, -2); /* previous value becomes first argument */ + n = pushnestedvalues(cs, 0); /* push nested captures */ + lua_call(L, n + 1, 1); /* call function */ + return 0; /* did not add any extra value */ +} + + +/* +** Select capture +*/ +static int numcap (CapState *cs) { + int idx = cs->cap->idx; /* value to select */ + if (idx == 0) { /* no values? */ + nextcap(cs); /* skip entire capture */ + return 0; /* no value produced */ + } + else { + int n = pushnestedvalues(cs, 0); + if (n < idx) /* invalid index? */ + return luaL_error(cs->L, "no capture '%d'", idx); + else { + lua_pushvalue(cs->L, -(n - idx + 1)); /* get selected capture */ + lua_replace(cs->L, -(n + 1)); /* put it in place of 1st capture */ + lua_pop(cs->L, n - 1); /* remove other captures */ + return 1; + } + } +} + + +/* +** Return the stack index of the first runtime capture in the given +** list of captures (or zero if no runtime captures) +*/ +int finddyncap (Capture *cap, Capture *last) { + for (; cap < last; cap++) { + if (cap->kind == Cruntime) + return cap->idx; /* stack position of first capture */ + } + return 0; /* no dynamic captures in this segment */ +} + + +/* +** Calls a runtime capture. Returns number of captures "removed" by the +** call, that is, those inside the group capture. Captures to be added +** are on the Lua stack. +*/ +int runtimecap (CapState *cs, Capture *close, const char *s, int *rem) { + int n, id; + lua_State *L = cs->L; + int otop = lua_gettop(L); + Capture *open = findopen(close); /* get open group capture */ + assert(captype(open) == Cgroup); + id = finddyncap(open, close); /* get first dynamic capture argument */ + close->kind = Cclose; /* closes the group */ + close->index = s - cs->s; + cs->cap = open; cs->valuecached = 0; /* prepare capture state */ + luaL_checkstack(L, 4, "too many runtime captures"); + pushluaval(cs); /* push function to be called */ + lua_pushvalue(L, SUBJIDX); /* push original subject */ + lua_pushinteger(L, s - cs->s + 1); /* push current position */ + n = pushnestedvalues(cs, 0); /* push nested captures */ + lua_call(L, n + 2, LUA_MULTRET); /* call dynamic function */ + if (id > 0) { /* are there old dynamic captures to be removed? */ + int i; + for (i = id; i <= otop; i++) + lua_remove(L, id); /* remove old dynamic captures */ + *rem = otop - id + 1; /* total number of dynamic captures removed */ + } + else + *rem = 0; /* no dynamic captures removed */ + return close - open - 1; /* number of captures to be removed */ +} + + +/* +** Auxiliary structure for substitution and string captures: keep +** information about nested captures for future use, avoiding to push +** string results into Lua +*/ +typedef struct StrAux { + int isstring; /* whether capture is a string */ + union { + Capture *cp; /* if not a string, respective capture */ + struct { /* if it is a string... */ + Index_t idx; /* starts here */ + Index_t siz; /* with this size */ + } s; + } u; +} StrAux; + +#define MAXSTRCAPS 10 + +/* +** Collect values from current capture into array 'cps'. Current +** capture must be Cstring (first call) or Csimple (recursive calls). +** (In first call, fills %0 with whole match for Cstring.) +** Returns number of elements in the array that were filled. +*/ +static int getstrcaps (CapState *cs, StrAux *cps, int n) { + int k = n++; + Capture *head = cs->cap++; + cps[k].isstring = 1; /* get string value */ + cps[k].u.s.idx = head->index; /* starts here */ + while (capinside(head, cs->cap)) { + if (n >= MAXSTRCAPS) /* too many captures? */ + nextcap(cs); /* skip extra captures (will not need them) */ + else if (captype(cs->cap) == Csimple) /* string? */ + n = getstrcaps(cs, cps, n); /* put info. into array */ + else { + cps[n].isstring = 0; /* not a string */ + cps[n].u.cp = cs->cap; /* keep original capture */ + nextcap(cs); + n++; + } + } + cps[k].u.s.siz = closesize(cs, head); + skipclose(cs, head); + return n; +} + + +/* +** add next capture value (which should be a string) to buffer 'b' +*/ +static int addonestring (luaL_Buffer *b, CapState *cs, const char *what); + + +/* +** String capture: add result to buffer 'b' (instead of pushing +** it into the stack) +*/ +static void stringcap (luaL_Buffer *b, CapState *cs) { + StrAux cps[MAXSTRCAPS]; + int n; + size_t len, i; + const char *fmt; /* format string */ + fmt = lua_tolstring(cs->L, updatecache(cs, cs->cap->idx), &len); + n = getstrcaps(cs, cps, 0) - 1; /* collect nested captures */ + for (i = 0; i < len; i++) { /* traverse format string */ + if (fmt[i] != '%') /* not an escape? */ + luaL_addchar(b, fmt[i]); /* add it to buffer */ + else if (fmt[++i] < '0' || fmt[i] > '9') /* not followed by a digit? */ + luaL_addchar(b, fmt[i]); /* add to buffer */ + else { + int l = fmt[i] - '0'; /* capture index */ + if (l > n) + luaL_error(cs->L, "invalid capture index (%d)", l); + else if (cps[l].isstring) + luaL_addlstring(b, cs->s + cps[l].u.s.idx, cps[l].u.s.siz); + else { + Capture *curr = cs->cap; + cs->cap = cps[l].u.cp; /* go back to evaluate that nested capture */ + if (!addonestring(b, cs, "capture")) + luaL_error(cs->L, "no values in capture index %d", l); + cs->cap = curr; /* continue from where it stopped */ + } + } + } +} + + +/* +** Substitution capture: add result to buffer 'b' +*/ +static void substcap (luaL_Buffer *b, CapState *cs) { + const char *curr = cs->s + cs->cap->index; + Capture *head = cs->cap++; + while (capinside(head, cs->cap)) { + Capture *cap = cs->cap; + const char *caps = cs->s + cap->index; + luaL_addlstring(b, curr, caps - curr); /* add text up to capture */ + if (addonestring(b, cs, "replacement")) + curr = caps + capsize(cap, cs->cap - 1); /* continue after match */ + else /* no capture value */ + curr = caps; /* keep original text in final result */ + } + /* add last piece of text */ + luaL_addlstring(b, curr, cs->s + head->index + closesize(cs, head) - curr); + skipclose(cs, head); +} + + +/* +** Evaluates a capture and adds its first value to buffer 'b'; returns +** whether there was a value +*/ +static int addonestring (luaL_Buffer *b, CapState *cs, const char *what) { + switch (captype(cs->cap)) { + case Cstring: + stringcap(b, cs); /* add capture directly to buffer */ + return 1; + case Csubst: + substcap(b, cs); /* add capture directly to buffer */ + return 1; + case Cacc: /* accumulator capture? */ + return luaL_error(cs->L, "invalid context for an accumulator capture"); + default: { + lua_State *L = cs->L; + int n = pushcapture(cs); + if (n > 0) { + if (n > 1) lua_pop(L, n - 1); /* only one result */ + if (!lua_isstring(L, -1)) + return luaL_error(L, "invalid %s value (a %s)", + what, luaL_typename(L, -1)); + luaL_addvalue(b); + } + return n; + } + } +} + + +#if !defined(MAXRECLEVEL) +#define MAXRECLEVEL 200 +#endif + + +/* +** Push all values of the current capture into the stack; returns +** number of values pushed +*/ +static int pushcapture (CapState *cs) { + lua_State *L = cs->L; + int res; + luaL_checkstack(L, 4, "too many captures"); + if (cs->reclevel++ > MAXRECLEVEL) + return luaL_error(L, "subcapture nesting too deep"); + switch (captype(cs->cap)) { + case Cposition: { + lua_pushinteger(L, cs->cap->index + 1); + cs->cap++; + res = 1; + break; + } + case Cconst: { + pushluaval(cs); + cs->cap++; + res = 1; + break; + } + case Carg: { + int arg = (cs->cap++)->idx; + if (arg + FIXEDARGS > cs->ptop) + return luaL_error(L, "reference to absent extra argument #%d", arg); + lua_pushvalue(L, arg + FIXEDARGS); + res = 1; + break; + } + case Csimple: { + int k = pushnestedvalues(cs, 1); + lua_insert(L, -k); /* make whole match be first result */ + res = k; + break; + } + case Cruntime: { + lua_pushvalue(L, (cs->cap++)->idx); /* value is in the stack */ + res = 1; + break; + } + case Cstring: { + luaL_Buffer b; + luaL_buffinit(L, &b); + stringcap(&b, cs); + luaL_pushresult(&b); + res = 1; + break; + } + case Csubst: { + luaL_Buffer b; + luaL_buffinit(L, &b); + substcap(&b, cs); + luaL_pushresult(&b); + res = 1; + break; + } + case Cgroup: { + if (cs->cap->idx == 0) /* anonymous group? */ + res = pushnestedvalues(cs, 0); /* add all nested values */ + else { /* named group: add no values */ + nextcap(cs); /* skip capture */ + res = 0; + } + break; + } + case Cbackref: res = backrefcap(cs); break; + case Ctable: res = tablecap(cs); break; + case Cfunction: res = functioncap(cs); break; + case Cacc: res = accumulatorcap(cs); break; + case Cnum: res = numcap(cs); break; + case Cquery: res = querycap(cs); break; + case Cfold: res = foldcap(cs); break; + default: assert(0); res = 0; + } + cs->reclevel--; + return res; +} + + +/* +** Prepare a CapState structure and traverse the entire list of +** captures in the stack pushing its results. 's' is the subject +** string, 'r' is the final position of the match, and 'ptop' +** the index in the stack where some useful values were pushed. +** Returns the number of results pushed. (If the list produces no +** results, push the final position of the match.) +*/ +int getcaptures (lua_State *L, const char *s, const char *r, int ptop) { + Capture *capture = (Capture *)lua_touserdata(L, caplistidx(ptop)); + int n = 0; + /* printcaplist(capture); */ + if (!isclosecap(capture)) { /* is there any capture? */ + CapState cs; + cs.ocap = cs.cap = capture; cs.L = L; cs.reclevel = 0; + cs.s = s; cs.valuecached = 0; cs.ptop = ptop; + cs.firstcap = lua_gettop(L) + 1; /* where first value (if any) will go */ + do { /* collect their values */ + n += pushcapture(&cs); + } while (!isclosecap(cs.cap)); + assert(lua_gettop(L) - cs.firstcap == n - 1); + } + if (n == 0) { /* no capture values? */ + lua_pushinteger(L, r - s + 1); /* return only end position */ + n = 1; + } + return n; +} + + diff --git a/thirdparty/lpeg/lpcap.h b/thirdparty/lpeg/lpcap.h new file mode 100644 index 000000000..abbd55371 --- /dev/null +++ b/thirdparty/lpeg/lpcap.h @@ -0,0 +1,86 @@ + +#if !defined(lpcap_h) +#define lpcap_h + + +#include "lptypes.h" + + +/* kinds of captures */ +typedef enum CapKind { + Cclose, /* not used in trees */ + Cposition, + Cconst, /* ktable[key] is Lua constant */ + Cbackref, /* ktable[key] is "name" of group to get capture */ + Carg, /* 'key' is arg's number */ + Csimple, /* next node is pattern */ + Ctable, /* next node is pattern */ + Cfunction, /* ktable[key] is function; next node is pattern */ + Cacc, /* ktable[key] is function; next node is pattern */ + Cquery, /* ktable[key] is table; next node is pattern */ + Cstring, /* ktable[key] is string; next node is pattern */ + Cnum, /* numbered capture; 'key' is number of value to return */ + Csubst, /* substitution capture; next node is pattern */ + Cfold, /* ktable[key] is function; next node is pattern */ + Cruntime, /* not used in trees (is uses another type for tree) */ + Cgroup /* ktable[key] is group's "name" */ +} CapKind; + + +/* +** An unsigned integer large enough to index any subject entirely. +** It can be size_t, but that will double the size of the array +** of captures in a 64-bit machine. +*/ +#if !defined(Index_t) +typedef uint Index_t; +#endif + +#define MAXINDT (~(Index_t)0) + + +typedef struct Capture { + Index_t index; /* subject position */ + unsigned short idx; /* extra info (group name, arg index, etc.) */ + byte kind; /* kind of capture */ + byte siz; /* size of full capture + 1 (0 = not a full capture) */ +} Capture; + + +typedef struct CapState { + Capture *cap; /* current capture */ + Capture *ocap; /* (original) capture list */ + lua_State *L; + int ptop; /* stack index of last argument to 'match' */ + int firstcap; /* stack index of first capture pushed in the stack */ + const char *s; /* original string */ + int valuecached; /* value stored in cache slot */ + int reclevel; /* recursion level */ +} CapState; + + +#define captype(cap) ((cap)->kind) + +#define isclosecap(cap) (captype(cap) == Cclose) +#define isopencap(cap) ((cap)->siz == 0) + +/* true if c2 is (any number of levels) inside c1 */ +#define capinside(c1,c2) \ + (isopencap(c1) ? !isclosecap(c2) \ + : (c2)->index < (c1)->index + (c1)->siz - 1) + + +/** +** Maximum number of captures to visit when looking for an 'open'. +*/ +#define MAXLOP 20 + + + +int runtimecap (CapState *cs, Capture *close, const char *s, int *rem); +int getcaptures (lua_State *L, const char *s, const char *r, int ptop); +int finddyncap (Capture *cap, Capture *last); + +#endif + + diff --git a/thirdparty/lpeg/lpcode.c b/thirdparty/lpeg/lpcode.c new file mode 100644 index 000000000..f3b8ae365 --- /dev/null +++ b/thirdparty/lpeg/lpcode.c @@ -0,0 +1,1051 @@ + +#include + + +#include "lua.h" +#include "lauxlib.h" + +#include "lptypes.h" +#include "lpcode.h" +#include "lpcset.h" + + +/* signals a "no-instruction */ +#define NOINST -1 + + + +static const Charset fullset_ = + {{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; + +static const Charset *fullset = &fullset_; + + +/* +** {====================================================== +** Analysis and some optimizations +** ======================================================= +*/ + + +/* +** A few basic operations on Charsets +*/ +static void cs_complement (Charset *cs) { + loopset(i, cs->cs[i] = ~cs->cs[i]); +} + +static int cs_disjoint (const Charset *cs1, const Charset *cs2) { + loopset(i, if ((cs1->cs[i] & cs2->cs[i]) != 0) return 0;) + return 1; +} + + +/* +** Visit a TCall node taking care to stop recursion. If node not yet +** visited, return 'f(sib2(tree))', otherwise return 'def' (default +** value) +*/ +static int callrecursive (TTree *tree, int f (TTree *t), int def) { + int key = tree->key; + assert(tree->tag == TCall); + assert(sib2(tree)->tag == TRule); + if (key == 0) /* node already visited? */ + return def; /* return default value */ + else { /* first visit */ + int result; + tree->key = 0; /* mark call as already visited */ + result = f(sib2(tree)); /* go to called rule */ + tree->key = key; /* restore tree */ + return result; + } +} + + +/* +** Check whether a pattern tree has captures +*/ +int hascaptures (TTree *tree) { + tailcall: + switch (tree->tag) { + case TCapture: case TRunTime: + return 1; + case TCall: + return callrecursive(tree, hascaptures, 0); + case TRule: /* do not follow siblings */ + tree = sib1(tree); goto tailcall; + case TOpenCall: assert(0); + default: { + switch (numsiblings[tree->tag]) { + case 1: /* return hascaptures(sib1(tree)); */ + tree = sib1(tree); goto tailcall; + case 2: + if (hascaptures(sib1(tree))) + return 1; + /* else return hascaptures(sib2(tree)); */ + tree = sib2(tree); goto tailcall; + default: assert(numsiblings[tree->tag] == 0); return 0; + } + } + } +} + + +/* +** Checks how a pattern behaves regarding the empty string, +** in one of two different ways: +** A pattern is *nullable* if it can match without consuming any character; +** A pattern is *nofail* if it never fails for any string +** (including the empty string). +** The difference is only for predicates and run-time captures; +** for other patterns, the two properties are equivalent. +** (With predicates, &'a' is nullable but not nofail. Of course, +** nofail => nullable.) +** These functions are all convervative in the following way: +** p is nullable => nullable(p) +** nofail(p) => p cannot fail +** The function assumes that TOpenCall is not nullable; +** this will be checked again when the grammar is fixed. +** Run-time captures can do whatever they want, so the result +** is conservative. +*/ +int checkaux (TTree *tree, int pred) { + tailcall: + switch (tree->tag) { + case TChar: case TSet: case TAny: case TUTFR: + case TFalse: case TOpenCall: + return 0; /* not nullable */ + case TRep: case TTrue: + return 1; /* no fail */ + case TNot: case TBehind: /* can match empty, but can fail */ + if (pred == PEnofail) return 0; + else return 1; /* PEnullable */ + case TAnd: /* can match empty; fail iff body does */ + if (pred == PEnullable) return 1; + /* else return checkaux(sib1(tree), pred); */ + tree = sib1(tree); goto tailcall; + case TRunTime: /* can fail; match empty iff body does */ + if (pred == PEnofail) return 0; + /* else return checkaux(sib1(tree), pred); */ + tree = sib1(tree); goto tailcall; + case TSeq: + if (!checkaux(sib1(tree), pred)) return 0; + /* else return checkaux(sib2(tree), pred); */ + tree = sib2(tree); goto tailcall; + case TChoice: + if (checkaux(sib2(tree), pred)) return 1; + /* else return checkaux(sib1(tree), pred); */ + tree = sib1(tree); goto tailcall; + case TCapture: case TGrammar: case TRule: case TXInfo: + /* return checkaux(sib1(tree), pred); */ + tree = sib1(tree); goto tailcall; + case TCall: /* return checkaux(sib2(tree), pred); */ + tree = sib2(tree); goto tailcall; + default: assert(0); return 0; + } +} + + +/* +** number of characters to match a pattern (or -1 if variable) +*/ +int fixedlen (TTree *tree) { + int len = 0; /* to accumulate in tail calls */ + tailcall: + switch (tree->tag) { + case TChar: case TSet: case TAny: + return len + 1; + case TUTFR: + return (tree->cap == sib1(tree)->cap) ? len + tree->cap : -1; + case TFalse: case TTrue: case TNot: case TAnd: case TBehind: + return len; + case TRep: case TRunTime: case TOpenCall: + return -1; + case TCapture: case TRule: case TGrammar: case TXInfo: + /* return fixedlen(sib1(tree)); */ + tree = sib1(tree); goto tailcall; + case TCall: { + int n1 = callrecursive(tree, fixedlen, -1); + if (n1 < 0) + return -1; + else + return len + n1; + } + case TSeq: { + int n1 = fixedlen(sib1(tree)); + if (n1 < 0) + return -1; + /* else return fixedlen(sib2(tree)) + len; */ + len += n1; tree = sib2(tree); goto tailcall; + } + case TChoice: { + int n1 = fixedlen(sib1(tree)); + int n2 = fixedlen(sib2(tree)); + if (n1 != n2 || n1 < 0) + return -1; + else + return len + n1; + } + default: assert(0); return 0; + }; +} + + +/* +** Computes the 'first set' of a pattern. +** The result is a conservative aproximation: +** match p ax -> x (for some x) ==> a belongs to first(p) +** or +** a not in first(p) ==> match p ax -> fail (for all x) +** +** The set 'follow' is the first set of what follows the +** pattern (full set if nothing follows it). +** +** The function returns 0 when this resulting set can be used for +** test instructions that avoid the pattern altogether. +** A non-zero return can happen for two reasons: +** 1) match p '' -> '' ==> return has bit 1 set +** (tests cannot be used because they would always fail for an empty input); +** 2) there is a match-time capture ==> return has bit 2 set +** (optimizations should not bypass match-time captures). +*/ +static int getfirst (TTree *tree, const Charset *follow, Charset *firstset) { + tailcall: + switch (tree->tag) { + case TChar: case TSet: case TAny: case TFalse: { + tocharset(tree, firstset); + return 0; + } + case TUTFR: { + int c; + clearset(firstset->cs); /* erase all chars */ + for (c = tree->key; c <= sib1(tree)->key; c++) + setchar(firstset->cs, c); + return 0; + } + case TTrue: { + loopset(i, firstset->cs[i] = follow->cs[i]); + return 1; /* accepts the empty string */ + } + case TChoice: { + Charset csaux; + int e1 = getfirst(sib1(tree), follow, firstset); + int e2 = getfirst(sib2(tree), follow, &csaux); + loopset(i, firstset->cs[i] |= csaux.cs[i]); + return e1 | e2; + } + case TSeq: { + if (!nullable(sib1(tree))) { + /* when p1 is not nullable, p2 has nothing to contribute; + return getfirst(sib1(tree), fullset, firstset); */ + tree = sib1(tree); follow = fullset; goto tailcall; + } + else { /* FIRST(p1 p2, fl) = FIRST(p1, FIRST(p2, fl)) */ + Charset csaux; + int e2 = getfirst(sib2(tree), follow, &csaux); + int e1 = getfirst(sib1(tree), &csaux, firstset); + if (e1 == 0) return 0; /* 'e1' ensures that first can be used */ + else if ((e1 | e2) & 2) /* one of the children has a matchtime? */ + return 2; /* pattern has a matchtime capture */ + else return e2; /* else depends on 'e2' */ + } + } + case TRep: { + getfirst(sib1(tree), follow, firstset); + loopset(i, firstset->cs[i] |= follow->cs[i]); + return 1; /* accept the empty string */ + } + case TCapture: case TGrammar: case TRule: case TXInfo: { + /* return getfirst(sib1(tree), follow, firstset); */ + tree = sib1(tree); goto tailcall; + } + case TRunTime: { /* function invalidates any follow info. */ + int e = getfirst(sib1(tree), fullset, firstset); + if (e) return 2; /* function is not "protected"? */ + else return 0; /* pattern inside capture ensures first can be used */ + } + case TCall: { + /* return getfirst(sib2(tree), follow, firstset); */ + tree = sib2(tree); goto tailcall; + } + case TAnd: { + int e = getfirst(sib1(tree), follow, firstset); + loopset(i, firstset->cs[i] &= follow->cs[i]); + return e; + } + case TNot: { + if (tocharset(sib1(tree), firstset)) { + cs_complement(firstset); + return 1; + } /* else */ + } /* FALLTHROUGH */ + case TBehind: { /* instruction gives no new information */ + /* call 'getfirst' only to check for math-time captures */ + int e = getfirst(sib1(tree), follow, firstset); + loopset(i, firstset->cs[i] = follow->cs[i]); /* uses follow */ + return e | 1; /* always can accept the empty string */ + } + default: assert(0); return 0; + } +} + + +/* +** If 'headfail(tree)' true, then 'tree' can fail only depending on the +** next character of the subject. +*/ +static int headfail (TTree *tree) { + tailcall: + switch (tree->tag) { + case TChar: case TSet: case TAny: case TFalse: + return 1; + case TTrue: case TRep: case TRunTime: case TNot: + case TBehind: case TUTFR: + return 0; + case TCapture: case TGrammar: case TRule: case TXInfo: case TAnd: + tree = sib1(tree); goto tailcall; /* return headfail(sib1(tree)); */ + case TCall: + tree = sib2(tree); goto tailcall; /* return headfail(sib2(tree)); */ + case TSeq: + if (!nofail(sib2(tree))) return 0; + /* else return headfail(sib1(tree)); */ + tree = sib1(tree); goto tailcall; + case TChoice: + if (!headfail(sib1(tree))) return 0; + /* else return headfail(sib2(tree)); */ + tree = sib2(tree); goto tailcall; + default: assert(0); return 0; + } +} + + +/* +** Check whether the code generation for the given tree can benefit +** from a follow set (to avoid computing the follow set when it is +** not needed) +*/ +static int needfollow (TTree *tree) { + tailcall: + switch (tree->tag) { + case TChar: case TSet: case TAny: case TUTFR: + case TFalse: case TTrue: case TAnd: case TNot: + case TRunTime: case TGrammar: case TCall: case TBehind: + return 0; + case TChoice: case TRep: + return 1; + case TCapture: + tree = sib1(tree); goto tailcall; + case TSeq: + tree = sib2(tree); goto tailcall; + default: assert(0); return 0; + } +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** Code generation +** ======================================================= +*/ + + +/* +** size of an instruction +*/ +int sizei (const Instruction *i) { + switch((Opcode)i->i.code) { + case ISet: case ISpan: return 1 + i->i.aux2.set.size; + case ITestSet: return 2 + i->i.aux2.set.size; + case ITestChar: case ITestAny: case IChoice: case IJmp: case ICall: + case IOpenCall: case ICommit: case IPartialCommit: case IBackCommit: + case IUTFR: + return 2; + default: return 1; + } +} + + +/* +** state for the compiler +*/ +typedef struct CompileState { + Pattern *p; /* pattern being compiled */ + int ncode; /* next position in p->code to be filled */ + lua_State *L; +} CompileState; + + +/* +** code generation is recursive; 'opt' indicates that the code is being +** generated as the last thing inside an optional pattern (so, if that +** code is optional too, it can reuse the 'IChoice' already in place for +** the outer pattern). 'tt' points to a previous test protecting this +** code (or NOINST). 'fl' is the follow set of the pattern. +*/ +static void codegen (CompileState *compst, TTree *tree, int opt, int tt, + const Charset *fl); + + +static void finishrelcode (lua_State *L, Pattern *p, Instruction *block, + int size) { + if (block == NULL) + luaL_error(L, "not enough memory"); + block->codesize = size; + p->code = (Instruction *)block + 1; +} + + +/* +** Initialize array 'p->code' +*/ +static void newcode (lua_State *L, Pattern *p, int size) { + void *ud; + Instruction *block; + lua_Alloc f = lua_getallocf(L, &ud); + size++; /* slot for 'codesize' */ + block = (Instruction*) f(ud, NULL, 0, size * sizeof(Instruction)); + finishrelcode(L, p, block, size); +} + + +void freecode (lua_State *L, Pattern *p) { + if (p->code != NULL) { + void *ud; + lua_Alloc f = lua_getallocf(L, &ud); + uint osize = p->code[-1].codesize; + f(ud, p->code - 1, osize * sizeof(Instruction), 0); /* free block */ + } +} + + +/* +** Assume that 'nsize' is not zero and that 'p->code' already exists. +*/ +static void realloccode (lua_State *L, Pattern *p, int nsize) { + void *ud; + lua_Alloc f = lua_getallocf(L, &ud); + Instruction *block = p->code - 1; + uint osize = block->codesize; + nsize++; /* add the 'codesize' slot to size */ + block = (Instruction*) f(ud, block, osize * sizeof(Instruction), + nsize * sizeof(Instruction)); + finishrelcode(L, p, block, nsize); +} + + +/* +** Add space for an instruction with 'n' slots and return its index. +*/ +static int nextinstruction (CompileState *compst, int n) { + int size = compst->p->code[-1].codesize - 1; + int ncode = compst->ncode; + if (ncode > size - n) { + uint nsize = size + (size >> 1) + n; + if (nsize >= INT_MAX) + luaL_error(compst->L, "pattern code too large"); + realloccode(compst->L, compst->p, nsize); + } + compst->ncode = ncode + n; + return ncode; +} + + +#define getinstr(cs,i) ((cs)->p->code[i]) + + +static int addinstruction (CompileState *compst, Opcode op, int aux) { + int i = nextinstruction(compst, 1); + getinstr(compst, i).i.code = op; + getinstr(compst, i).i.aux1 = aux; + return i; +} + + +/* +** Add an instruction followed by space for an offset (to be set later) +*/ +static int addoffsetinst (CompileState *compst, Opcode op) { + int i = addinstruction(compst, op, 0); /* instruction */ + addinstruction(compst, (Opcode)0, 0); /* open space for offset */ + assert(op == ITestSet || sizei(&getinstr(compst, i)) == 2); + return i; +} + + +/* +** Set the offset of an instruction +*/ +static void setoffset (CompileState *compst, int instruction, int offset) { + getinstr(compst, instruction + 1).offset = offset; +} + + +static void codeutfr (CompileState *compst, TTree *tree) { + int i = addoffsetinst(compst, IUTFR); + int to = sib1(tree)->u.n; + assert(sib1(tree)->tag == TXInfo); + getinstr(compst, i + 1).offset = tree->u.n; + getinstr(compst, i).i.aux1 = to & 0xff; + getinstr(compst, i).i.aux2.key = to >> 8; +} + + +/* +** Add a capture instruction: +** 'op' is the capture instruction; 'cap' the capture kind; +** 'key' the key into ktable; 'aux' is the optional capture offset +** +*/ +static int addinstcap (CompileState *compst, Opcode op, int cap, int key, + int aux) { + int i = addinstruction(compst, op, joinkindoff(cap, aux)); + getinstr(compst, i).i.aux2.key = key; + return i; +} + + +#define gethere(compst) ((compst)->ncode) + +#define target(code,i) ((i) + code[i + 1].offset) + + +/* +** Patch 'instruction' to jump to 'target' +*/ +static void jumptothere (CompileState *compst, int instruction, int target) { + if (instruction >= 0) + setoffset(compst, instruction, target - instruction); +} + + +/* +** Patch 'instruction' to jump to current position +*/ +static void jumptohere (CompileState *compst, int instruction) { + jumptothere(compst, instruction, gethere(compst)); +} + + +/* +** Code an IChar instruction, or IAny if there is an equivalent +** test dominating it +*/ +static void codechar (CompileState *compst, int c, int tt) { + if (tt >= 0 && getinstr(compst, tt).i.code == ITestChar && + getinstr(compst, tt).i.aux1 == c) + addinstruction(compst, IAny, 0); + else + addinstruction(compst, IChar, c); +} + + +/* +** Add a charset posfix to an instruction. +*/ +static void addcharset (CompileState *compst, int inst, charsetinfo *info) { + int p; + Instruction *I = &getinstr(compst, inst); + byte *charset; + int isize = instsize(info->size); /* size in instructions */ + int i; + I->i.aux2.set.offset = info->offset * 8; /* offset in bits */ + I->i.aux2.set.size = isize; + I->i.aux1 = info->deflt; + p = nextinstruction(compst, isize); /* space for charset */ + charset = getinstr(compst, p).buff; /* charset buffer */ + for (i = 0; i < isize * (int)sizeof(Instruction); i++) + charset[i] = getbytefromcharset(info, i); /* copy the buffer */ +} + + +/* +** Check whether charset 'info' is dominated by instruction 'p' +*/ +static int cs_equal (Instruction *p, charsetinfo *info) { + if (p->i.code != ITestSet) + return 0; + else if (p->i.aux2.set.offset != info->offset * 8 || + p->i.aux2.set.size != instsize(info->size) || + p->i.aux1 != info->deflt) + return 0; + else { + int i; + for (i = 0; i < instsize(info->size) * (int)sizeof(Instruction); i++) { + if ((p + 2)->buff[i] != getbytefromcharset(info, i)) + return 0; + } + } + return 1; +} + + +/* +** Code a char set, using IAny when instruction is dominated by an +** equivalent test. +*/ +static void codecharset (CompileState *compst, TTree *tree, int tt) { + charsetinfo info; + tree2cset(tree, &info); + if (tt >= 0 && cs_equal(&getinstr(compst, tt), &info)) + addinstruction(compst, IAny, 0); + else { + int i = addinstruction(compst, ISet, 0); + addcharset(compst, i, &info); + } +} + + +/* +** Code a test set, optimizing unit sets for ITestChar, "complete" +** sets for ITestAny, and empty sets for IJmp (always fails). +** 'e' is true iff test should accept the empty string. (Test +** instructions in the current VM never accept the empty string.) +*/ +static int codetestset (CompileState *compst, Charset *cs, int e) { + if (e) return NOINST; /* no test */ + else { + charsetinfo info; + Opcode op = charsettype(cs->cs, &info); + switch (op) { + case IFail: return addoffsetinst(compst, IJmp); /* always jump */ + case IAny: return addoffsetinst(compst, ITestAny); + case IChar: { + int i = addoffsetinst(compst, ITestChar); + getinstr(compst, i).i.aux1 = info.offset; + return i; + } + default: { /* regular set */ + int i = addoffsetinst(compst, ITestSet); + addcharset(compst, i, &info); + assert(op == ISet); + return i; + } + } + } +} + + +/* +** Find the final destination of a sequence of jumps +*/ +static int finaltarget (Instruction *code, int i) { + while (code[i].i.code == IJmp) + i = target(code, i); + return i; +} + + +/* +** final label (after traversing any jumps) +*/ +static int finallabel (Instruction *code, int i) { + return finaltarget(code, target(code, i)); +} + + +/* +** == behind n;

(where n = fixedlen(p)) +*/ +static void codebehind (CompileState *compst, TTree *tree) { + if (tree->u.n > 0) + addinstruction(compst, IBehind, tree->u.n); + codegen(compst, sib1(tree), 0, NOINST, fullset); +} + + +/* +** Choice; optimizations: +** - when p1 is headfail or when first(p1) and first(p2) are disjoint, +** than a character not in first(p1) cannot go to p1 and a character +** in first(p1) cannot go to p2, either because p1 will accept +** (headfail) or because it is not in first(p2) (disjoint). +** (The second case is not valid if p1 accepts the empty string, +** as then there is no character at all...) +** - when p2 is empty and opt is true; a IPartialCommit can reuse +** the Choice already active in the stack. +*/ +static void codechoice (CompileState *compst, TTree *p1, TTree *p2, int opt, + const Charset *fl) { + int emptyp2 = (p2->tag == TTrue); + Charset cs1, cs2; + int e1 = getfirst(p1, fullset, &cs1); + if (headfail(p1) || + (!e1 && (getfirst(p2, fl, &cs2), cs_disjoint(&cs1, &cs2)))) { + /* == test (fail(p1)) -> L1 ; p1 ; jmp L2; L1: p2; L2: */ + int test = codetestset(compst, &cs1, 0); + int jmp = NOINST; + codegen(compst, p1, 0, test, fl); + if (!emptyp2) + jmp = addoffsetinst(compst, IJmp); + jumptohere(compst, test); + codegen(compst, p2, opt, NOINST, fl); + jumptohere(compst, jmp); + } + else if (opt && emptyp2) { + /* p1? == IPartialCommit; p1 */ + jumptohere(compst, addoffsetinst(compst, IPartialCommit)); + codegen(compst, p1, 1, NOINST, fullset); + } + else { + /* == + test(first(p1)) -> L1; choice L1; ; commit L2; L1: ; L2: */ + int pcommit; + int test = codetestset(compst, &cs1, e1); + int pchoice = addoffsetinst(compst, IChoice); + codegen(compst, p1, emptyp2, test, fullset); + pcommit = addoffsetinst(compst, ICommit); + jumptohere(compst, pchoice); + jumptohere(compst, test); + codegen(compst, p2, opt, NOINST, fl); + jumptohere(compst, pcommit); + } +} + + +/* +** And predicate +** optimization: fixedlen(p) = n ==> <&p> ==

; behind n +** (valid only when 'p' has no captures) +*/ +static void codeand (CompileState *compst, TTree *tree, int tt) { + int n = fixedlen(tree); + if (n >= 0 && n <= MAXBEHIND && !hascaptures(tree)) { + codegen(compst, tree, 0, tt, fullset); + if (n > 0) + addinstruction(compst, IBehind, n); + } + else { /* default: Choice L1; p1; BackCommit L2; L1: Fail; L2: */ + int pcommit; + int pchoice = addoffsetinst(compst, IChoice); + codegen(compst, tree, 0, tt, fullset); + pcommit = addoffsetinst(compst, IBackCommit); + jumptohere(compst, pchoice); + addinstruction(compst, IFail, 0); + jumptohere(compst, pcommit); + } +} + + +/* +** Captures: if pattern has fixed (and not too big) length, and it +** has no nested captures, use a single IFullCapture instruction +** after the match; otherwise, enclose the pattern with OpenCapture - +** CloseCapture. +*/ +static void codecapture (CompileState *compst, TTree *tree, int tt, + const Charset *fl) { + int len = fixedlen(sib1(tree)); + if (len >= 0 && len <= MAXOFF && !hascaptures(sib1(tree))) { + codegen(compst, sib1(tree), 0, tt, fl); + addinstcap(compst, IFullCapture, tree->cap, tree->key, len); + } + else { + addinstcap(compst, IOpenCapture, tree->cap, tree->key, 0); + codegen(compst, sib1(tree), 0, tt, fl); + addinstcap(compst, ICloseCapture, Cclose, 0, 0); + } +} + + +static void coderuntime (CompileState *compst, TTree *tree, int tt) { + addinstcap(compst, IOpenCapture, Cgroup, tree->key, 0); + codegen(compst, sib1(tree), 0, tt, fullset); + addinstcap(compst, ICloseRunTime, Cclose, 0, 0); +} + + +/* +** Create a jump to 'test' and fix 'test' to jump to next instruction +*/ +static void closeloop (CompileState *compst, int test) { + int jmp = addoffsetinst(compst, IJmp); + jumptohere(compst, test); + jumptothere(compst, jmp, test); +} + + +/* +** Try repetition of charsets: +** For an empty set, repetition of fail is a no-op; +** For any or char, code a tight loop; +** For generic charset, use a span instruction. +*/ +static int coderepcharset (CompileState *compst, TTree *tree) { + switch (tree->tag) { + case TFalse: return 1; /* 'fail*' is a no-op */ + case TAny: { /* L1: testany -> L2; any; jmp L1; L2: */ + int test = addoffsetinst(compst, ITestAny); + addinstruction(compst, IAny, 0); + closeloop(compst, test); + return 1; + } + case TChar: { /* L1: testchar c -> L2; any; jmp L1; L2: */ + int test = addoffsetinst(compst, ITestChar); + getinstr(compst, test).i.aux1 = tree->u.n; + addinstruction(compst, IAny, 0); + closeloop(compst, test); + return 1; + } + case TSet: { /* regular set */ + charsetinfo info; + int i = addinstruction(compst, ISpan, 0); + tree2cset(tree, &info); + addcharset(compst, i, &info); + return 1; + } + default: return 0; /* not a charset */ + } +} + + +/* +** Repetion; optimizations: +** When pattern is a charset, use special code. +** When pattern is head fail, or if it starts with characters that +** are disjoint from what follows the repetions, a simple test +** is enough (a fail inside the repetition would backtrack to fail +** again in the following pattern, so there is no need for a choice). +** When 'opt' is true, the repetion can reuse the Choice already +** active in the stack. +*/ +static void coderep (CompileState *compst, TTree *tree, int opt, + const Charset *fl) { + if (!coderepcharset(compst, tree)) { + Charset st; + int e1 = getfirst(tree, fullset, &st); + if (headfail(tree) || (!e1 && cs_disjoint(&st, fl))) { + /* L1: test (fail(p1)) -> L2;

; jmp L1; L2: */ + int test = codetestset(compst, &st, 0); + codegen(compst, tree, 0, test, fullset); + closeloop(compst, test); + } + else { + /* test(fail(p1)) -> L2; choice L2; L1:

; partialcommit L1; L2: */ + /* or (if 'opt'): partialcommit L1; L1:

; partialcommit L1; */ + int commit, l2; + int test = codetestset(compst, &st, e1); + int pchoice = NOINST; + if (opt) + jumptohere(compst, addoffsetinst(compst, IPartialCommit)); + else + pchoice = addoffsetinst(compst, IChoice); + l2 = gethere(compst); + codegen(compst, tree, 0, NOINST, fullset); + commit = addoffsetinst(compst, IPartialCommit); + jumptothere(compst, commit, l2); + jumptohere(compst, pchoice); + jumptohere(compst, test); + } + } +} + + +/* +** Not predicate; optimizations: +** In any case, if first test fails, 'not' succeeds, so it can jump to +** the end. If pattern is headfail, that is all (it cannot fail +** in other parts); this case includes 'not' of simple sets. Otherwise, +** use the default code (a choice plus a failtwice). +*/ +static void codenot (CompileState *compst, TTree *tree) { + Charset st; + int e = getfirst(tree, fullset, &st); + int test = codetestset(compst, &st, e); + if (headfail(tree)) /* test (fail(p1)) -> L1; fail; L1: */ + addinstruction(compst, IFail, 0); + else { + /* test(fail(p))-> L1; choice L1;

; failtwice; L1: */ + int pchoice = addoffsetinst(compst, IChoice); + codegen(compst, tree, 0, NOINST, fullset); + addinstruction(compst, IFailTwice, 0); + jumptohere(compst, pchoice); + } + jumptohere(compst, test); +} + + +/* +** change open calls to calls, using list 'positions' to find +** correct offsets; also optimize tail calls +*/ +static void correctcalls (CompileState *compst, int *positions, + int from, int to) { + int i; + Instruction *code = compst->p->code; + for (i = from; i < to; i += sizei(&code[i])) { + if (code[i].i.code == IOpenCall) { + int n = code[i].i.aux2.key; /* rule number */ + int rule = positions[n]; /* rule position */ + assert(rule == from || code[rule - 1].i.code == IRet); + if (code[finaltarget(code, i + 2)].i.code == IRet) /* call; ret ? */ + code[i].i.code = IJmp; /* tail call */ + else + code[i].i.code = ICall; + jumptothere(compst, i, rule); /* call jumps to respective rule */ + } + } + assert(i == to); +} + + +/* +** Code for a grammar: +** call L1; jmp L2; L1: rule 1; ret; rule 2; ret; ...; L2: +*/ +static void codegrammar (CompileState *compst, TTree *grammar) { + int positions[MAXRULES]; + int rulenumber = 0; + TTree *rule; + int firstcall = addoffsetinst(compst, ICall); /* call initial rule */ + int jumptoend = addoffsetinst(compst, IJmp); /* jump to the end */ + int start = gethere(compst); /* here starts the initial rule */ + jumptohere(compst, firstcall); + for (rule = sib1(grammar); rule->tag == TRule; rule = sib2(rule)) { + TTree *r = sib1(rule); + assert(r->tag == TXInfo); + positions[rulenumber++] = gethere(compst); /* save rule position */ + codegen(compst, sib1(r), 0, NOINST, fullset); /* code rule */ + addinstruction(compst, IRet, 0); + } + assert(rule->tag == TTrue); + jumptohere(compst, jumptoend); + correctcalls(compst, positions, start, gethere(compst)); +} + + +static void codecall (CompileState *compst, TTree *call) { + int c = addoffsetinst(compst, IOpenCall); /* to be corrected later */ + assert(sib1(sib2(call))->tag == TXInfo); + getinstr(compst, c).i.aux2.key = sib1(sib2(call))->u.n; /* rule number */ +} + + +/* +** Code first child of a sequence +** (second child is called in-place to allow tail call) +** Return 'tt' for second child +*/ +static int codeseq1 (CompileState *compst, TTree *p1, TTree *p2, + int tt, const Charset *fl) { + if (needfollow(p1)) { + Charset fl1; + getfirst(p2, fl, &fl1); /* p1 follow is p2 first */ + codegen(compst, p1, 0, tt, &fl1); + } + else /* use 'fullset' as follow */ + codegen(compst, p1, 0, tt, fullset); + if (fixedlen(p1) != 0) /* can 'p1' consume anything? */ + return NOINST; /* invalidate test */ + else return tt; /* else 'tt' still protects sib2 */ +} + + +/* +** Main code-generation function: dispatch to auxiliar functions +** according to kind of tree. ('needfollow' should return true +** only for consructions that use 'fl'.) +*/ +static void codegen (CompileState *compst, TTree *tree, int opt, int tt, + const Charset *fl) { + tailcall: + switch (tree->tag) { + case TChar: codechar(compst, tree->u.n, tt); break; + case TAny: addinstruction(compst, IAny, 0); break; + case TSet: codecharset(compst, tree, tt); break; + case TTrue: break; + case TFalse: addinstruction(compst, IFail, 0); break; + case TUTFR: codeutfr(compst, tree); break; + case TChoice: codechoice(compst, sib1(tree), sib2(tree), opt, fl); break; + case TRep: coderep(compst, sib1(tree), opt, fl); break; + case TBehind: codebehind(compst, tree); break; + case TNot: codenot(compst, sib1(tree)); break; + case TAnd: codeand(compst, sib1(tree), tt); break; + case TCapture: codecapture(compst, tree, tt, fl); break; + case TRunTime: coderuntime(compst, tree, tt); break; + case TGrammar: codegrammar(compst, tree); break; + case TCall: codecall(compst, tree); break; + case TSeq: { + tt = codeseq1(compst, sib1(tree), sib2(tree), tt, fl); /* code 'p1' */ + /* codegen(compst, p2, opt, tt, fl); */ + tree = sib2(tree); goto tailcall; + } + default: assert(0); + } +} + + +/* +** Optimize jumps and other jump-like instructions. +** * Update labels of instructions with labels to their final +** destinations (e.g., choice L1; ... L1: jmp L2: becomes +** choice L2) +** * Jumps to other instructions that do jumps become those +** instructions (e.g., jump to return becomes a return; jump +** to commit becomes a commit) +*/ +static void peephole (CompileState *compst) { + Instruction *code = compst->p->code; + int i; + for (i = 0; i < compst->ncode; i += sizei(&code[i])) { + redo: + switch (code[i].i.code) { + case IChoice: case ICall: case ICommit: case IPartialCommit: + case IBackCommit: case ITestChar: case ITestSet: + case ITestAny: { /* instructions with labels */ + jumptothere(compst, i, finallabel(code, i)); /* optimize label */ + break; + } + case IJmp: { + int ft = finaltarget(code, i); + switch (code[ft].i.code) { /* jumping to what? */ + case IRet: case IFail: case IFailTwice: + case IEnd: { /* instructions with unconditional implicit jumps */ + code[i] = code[ft]; /* jump becomes that instruction */ + code[i + 1].i.code = IEmpty; /* 'no-op' for target position */ + break; + } + case ICommit: case IPartialCommit: + case IBackCommit: { /* inst. with unconditional explicit jumps */ + int fft = finallabel(code, ft); + code[i] = code[ft]; /* jump becomes that instruction... */ + jumptothere(compst, i, fft); /* but must correct its offset */ + goto redo; /* reoptimize its label */ + } + default: { + jumptothere(compst, i, ft); /* optimize label */ + break; + } + } + break; + } + default: break; + } + } + assert(code[i - 1].i.code == IEnd); +} + + +/* +** Compile a pattern. 'size' is the size of the pattern's tree, +** which gives a hint for the size of the final code. +*/ +Instruction *compile (lua_State *L, Pattern *p, uint size) { + CompileState compst; + compst.p = p; compst.ncode = 0; compst.L = L; + newcode(L, p, size/2u + 2); /* set initial size */ + codegen(&compst, p->tree, 0, NOINST, fullset); + addinstruction(&compst, IEnd, 0); + realloccode(L, p, compst.ncode); /* set final size */ + peephole(&compst); + return p->code; +} + + +/* }====================================================== */ + diff --git a/thirdparty/lpeg/lpcode.h b/thirdparty/lpeg/lpcode.h new file mode 100644 index 000000000..10c2ced5b --- /dev/null +++ b/thirdparty/lpeg/lpcode.h @@ -0,0 +1,36 @@ + +#if !defined(lpcode_h) +#define lpcode_h + +#include "lua.h" + +#include "lptypes.h" +#include "lptree.h" +#include "lpvm.h" + +int checkaux (TTree *tree, int pred); +int fixedlen (TTree *tree); +int hascaptures (TTree *tree); +int lp_gc (lua_State *L); +Instruction *compile (lua_State *L, Pattern *p, uint size); +void freecode (lua_State *L, Pattern *p); +int sizei (const Instruction *i); + + +#define PEnullable 0 +#define PEnofail 1 + +/* +** nofail(t) implies that 't' cannot fail with any input +*/ +#define nofail(t) checkaux(t, PEnofail) + +/* +** (not nullable(t)) implies 't' cannot match without consuming +** something +*/ +#define nullable(t) checkaux(t, PEnullable) + + + +#endif diff --git a/thirdparty/lpeg/lpcset.c b/thirdparty/lpeg/lpcset.c new file mode 100644 index 000000000..2dcffd9a4 --- /dev/null +++ b/thirdparty/lpeg/lpcset.c @@ -0,0 +1,110 @@ + +#include "lptypes.h" +#include "lpcset.h" + + +/* +** Add to 'c' the index of the (only) bit set in byte 'b' +*/ +static int onlybit (int c, int b) { + if ((b & 0xF0) != 0) { c += 4; b >>= 4; } + if ((b & 0x0C) != 0) { c += 2; b >>= 2; } + if ((b & 0x02) != 0) { c += 1; } + return c; +} + + +/* +** Check whether a charset is empty (returns IFail), singleton (IChar), +** full (IAny), or none of those (ISet). When singleton, 'info.offset' +** returns which character it is. When generic set, 'info' returns +** information about its range. +*/ +Opcode charsettype (const byte *cs, charsetinfo *info) { + int low0, low1, high0, high1; + for (low1 = 0; low1 < CHARSETSIZE && cs[low1] == 0; low1++) + /* find lowest byte with a 1-bit */; + if (low1 == CHARSETSIZE) + return IFail; /* no characters in set */ + for (high1 = CHARSETSIZE - 1; cs[high1] == 0; high1--) + /* find highest byte with a 1-bit; low1 is a sentinel */; + if (low1 == high1) { /* only one byte with 1-bits? */ + int b = cs[low1]; + if ((b & (b - 1)) == 0) { /* does byte has only one 1-bit? */ + info->offset = onlybit(low1 * BITSPERCHAR, b); /* get that bit */ + return IChar; /* single character */ + } + } + for (low0 = 0; low0 < CHARSETSIZE && cs[low0] == 0xFF; low0++) + /* find lowest byte with a 0-bit */; + if (low0 == CHARSETSIZE) + return IAny; /* set has all bits set */ + for (high0 = CHARSETSIZE - 1; cs[high0] == 0xFF; high0--) + /* find highest byte with a 0-bit; low0 is a sentinel */; + if (high1 - low1 <= high0 - low0) { /* range of 1s smaller than of 0s? */ + info->offset = low1; + info->size = high1 - low1 + 1; + info->deflt = 0; /* all discharged bits were 0 */ + } + else { + info->offset = low0; + info->size = high0 - low0 + 1; + info->deflt = 0xFF; /* all discharged bits were 1 */ + } + info->cs = cs + info->offset; + return ISet; +} + + +/* +** Get a byte from a compact charset. If index is inside the charset +** range, get the byte from the supporting charset (correcting it +** by the offset). Otherwise, return the default for the set. +*/ +byte getbytefromcharset (const charsetinfo *info, int index) { + if (index < info->size) + return info->cs[index]; + else return info->deflt; +} + + +/* +** If 'tree' is a 'char' pattern (TSet, TChar, TAny, TFalse), convert it +** into a charset and return 1; else return 0. +*/ +int tocharset (TTree *tree, Charset *cs) { + switch (tree->tag) { + case TChar: { /* only one char */ + assert(0 <= tree->u.n && tree->u.n <= UCHAR_MAX); + clearset(cs->cs); /* erase all chars */ + setchar(cs->cs, tree->u.n); /* add that one */ + return 1; + } + case TAny: { + fillset(cs->cs, 0xFF); /* add all characters to the set */ + return 1; + } + case TFalse: { + clearset(cs->cs); /* empty set */ + return 1; + } + case TSet: { /* fill set */ + int i; + fillset(cs->cs, tree->u.set.deflt); + for (i = 0; i < tree->u.set.size; i++) + cs->cs[tree->u.set.offset + i] = treebuffer(tree)[i]; + return 1; + } + default: return 0; + } +} + + +void tree2cset (TTree *tree, charsetinfo *info) { + assert(tree->tag == TSet); + info->offset = tree->u.set.offset; + info->size = tree->u.set.size; + info->deflt = tree->u.set.deflt; + info->cs = treebuffer(tree); +} + diff --git a/thirdparty/lpeg/lpcset.h b/thirdparty/lpeg/lpcset.h new file mode 100644 index 000000000..b69fef900 --- /dev/null +++ b/thirdparty/lpeg/lpcset.h @@ -0,0 +1,30 @@ + +#if !defined(lpset_h) +#define lpset_h + +#include "lpcset.h" +#include "lpcode.h" +#include "lptree.h" + + +/* +** Extra information for the result of 'charsettype'. When result is +** IChar, 'offset' is the character. When result is ISet, 'cs' is the +** supporting bit array (with offset included), 'offset' is the offset +** (in bytes), 'size' is the size (in bytes), and 'delt' is the default +** value for bytes outside the set. +*/ +typedef struct { + const byte *cs; + int offset; + int size; + int deflt; +} charsetinfo; + + +int tocharset (TTree *tree, Charset *cs); +Opcode charsettype (const byte *cs, charsetinfo *info); +byte getbytefromcharset (const charsetinfo *info, int index); +void tree2cset (TTree *tree, charsetinfo *info); + +#endif diff --git a/thirdparty/lpeg/lpprint.c b/thirdparty/lpeg/lpprint.c new file mode 100644 index 000000000..da902e6f7 --- /dev/null +++ b/thirdparty/lpeg/lpprint.c @@ -0,0 +1,298 @@ + +#include +#include +#include + + +#include "lptypes.h" +#include "lpprint.h" +#include "lpcode.h" + + +#if defined(LPEG_DEBUG) + +/* +** {====================================================== +** Printing patterns (for debugging) +** ======================================================= +*/ + + +void printcharset (const byte *st) { + int i; + printf("["); + for (i = 0; i <= UCHAR_MAX; i++) { + int first = i; + while (i <= UCHAR_MAX && testchar(st, i)) i++; + if (i - 1 == first) /* unary range? */ + printf("(%02x)", first); + else if (i - 1 > first) /* non-empty range? */ + printf("(%02x-%02x)", first, i - 1); + } + printf("]"); +} + + +static void printIcharset (const Instruction *inst, const byte *buff) { + byte cs[CHARSETSIZE]; + int i; + printf("(%02x-%d) ", inst->i.aux2.set.offset, inst->i.aux2.set.size); + clearset(cs); + for (i = 0; i < CHARSETSIZE * 8; i++) { + if (charinset(inst, buff, i)) + setchar(cs, i); + } + printcharset(cs); +} + + +static void printTcharset (TTree *tree) { + byte cs[CHARSETSIZE]; + int i; + printf("(%02x-%d) ", tree->u.set.offset, tree->u.set.size); + fillset(cs, tree->u.set.deflt); + for (i = 0; i < tree->u.set.size; i++) + cs[tree->u.set.offset + i] = treebuffer(tree)[i]; + printcharset(cs); +} + + +static const char *capkind (int kind) { + const char *const modes[] = { + "close", "position", "constant", "backref", + "argument", "simple", "table", "function", "accumulator", + "query", "string", "num", "substitution", "fold", + "runtime", "group"}; + return modes[kind]; +} + + +static void printjmp (const Instruction *op, const Instruction *p) { + printf("-> %d", (int)(p + (p + 1)->offset - op)); +} + + +void printinst (const Instruction *op, const Instruction *p) { + const char *const names[] = { + "any", "char", "set", + "testany", "testchar", "testset", + "span", "utf-range", "behind", + "ret", "end", + "choice", "jmp", "call", "open_call", + "commit", "partial_commit", "back_commit", "failtwice", "fail", "giveup", + "fullcapture", "opencapture", "closecapture", "closeruntime", + "--" + }; + printf("%02ld: %s ", (long)(p - op), names[p->i.code]); + switch ((Opcode)p->i.code) { + case IChar: { + printf("'%c' (%02x)", p->i.aux1, p->i.aux1); + break; + } + case ITestChar: { + printf("'%c' (%02x)", p->i.aux1, p->i.aux1); printjmp(op, p); + break; + } + case IUTFR: { + printf("%d - %d", p[1].offset, utf_to(p)); + break; + } + case IFullCapture: { + printf("%s (size = %d) (idx = %d)", + capkind(getkind(p)), getoff(p), p->i.aux2.key); + break; + } + case IOpenCapture: { + printf("%s (idx = %d)", capkind(getkind(p)), p->i.aux2.key); + break; + } + case ISet: { + printIcharset(p, (p+1)->buff); + break; + } + case ITestSet: { + printIcharset(p, (p+2)->buff); printjmp(op, p); + break; + } + case ISpan: { + printIcharset(p, (p+1)->buff); + break; + } + case IOpenCall: { + printf("-> %d", (p + 1)->offset); + break; + } + case IBehind: { + printf("%d", p->i.aux1); + break; + } + case IJmp: case ICall: case ICommit: case IChoice: + case IPartialCommit: case IBackCommit: case ITestAny: { + printjmp(op, p); + break; + } + default: break; + } + printf("\n"); +} + + +void printpatt (Instruction *p) { + Instruction *op = p; + uint n = op[-1].codesize - 1; + while (p < op + n) { + printinst(op, p); + p += sizei(p); + } +} + + +static void printcap (Capture *cap, int ident) { + while (ident--) printf(" "); + printf("%s (idx: %d - size: %d) -> %lu (%p)\n", + capkind(cap->kind), cap->idx, cap->siz, (long)cap->index, (void*)cap); +} + + +/* +** Print a capture and its nested captures +*/ +static Capture *printcap2close (Capture *cap, int ident) { + Capture *head = cap++; + printcap(head, ident); /* print head capture */ + while (capinside(head, cap)) + cap = printcap2close(cap, ident + 2); /* print nested captures */ + if (isopencap(head)) { + assert(isclosecap(cap)); + printcap(cap++, ident); /* print and skip close capture */ + } + return cap; +} + + +void printcaplist (Capture *cap) { + { /* for debugging, print first a raw list of captures */ + Capture *c = cap; + while (c->index != MAXINDT) { printcap(c, 0); c++; } + } + printf(">======\n"); + while (!isclosecap(cap)) + cap = printcap2close(cap, 0); + printf("=======\n"); +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Printing trees (for debugging) +** ======================================================= +*/ + +static const char *tagnames[] = { + "char", "set", "any", + "true", "false", "utf8.range", + "rep", + "seq", "choice", + "not", "and", + "call", "opencall", "rule", "xinfo", "grammar", + "behind", + "capture", "run-time" +}; + + +void printtree (TTree *tree, int ident) { + int i; + int sibs = numsiblings[tree->tag]; + for (i = 0; i < ident; i++) printf(" "); + printf("%s", tagnames[tree->tag]); + switch (tree->tag) { + case TChar: { + int c = tree->u.n; + if (isprint(c)) + printf(" '%c'\n", c); + else + printf(" (%02X)\n", c); + break; + } + case TSet: { + printTcharset(tree); + printf("\n"); + break; + } + case TUTFR: { + assert(sib1(tree)->tag == TXInfo); + printf(" %d (%02x %d) - %d (%02x %d) \n", + tree->u.n, tree->key, tree->cap, + sib1(tree)->u.n, sib1(tree)->key, sib1(tree)->cap); + break; + } + case TOpenCall: case TCall: { + assert(sib1(sib2(tree))->tag == TXInfo); + printf(" key: %d (rule: %d)\n", tree->key, sib1(sib2(tree))->u.n); + break; + } + case TBehind: { + printf(" %d\n", tree->u.n); + break; + } + case TCapture: { + printf(" kind: '%s' key: %d\n", capkind(tree->cap), tree->key); + break; + } + case TRule: { + printf(" key: %d\n", tree->key); + sibs = 1; /* do not print 'sib2' (next rule) as a sibling */ + break; + } + case TXInfo: { + printf(" n: %d\n", tree->u.n); + break; + } + case TGrammar: { + TTree *rule = sib1(tree); + printf(" %d\n", tree->u.n); /* number of rules */ + for (i = 0; i < tree->u.n; i++) { + printtree(rule, ident + 2); + rule = sib2(rule); + } + assert(rule->tag == TTrue); /* sentinel */ + sibs = 0; /* siblings already handled */ + break; + } + default: + printf("\n"); + break; + } + if (sibs >= 1) { + printtree(sib1(tree), ident + 2); + if (sibs >= 2) + printtree(sib2(tree), ident + 2); + } +} + + +void printktable (lua_State *L, int idx) { + int n, i; + lua_getuservalue(L, idx); + if (lua_isnil(L, -1)) /* no ktable? */ + return; + n = lua_rawlen(L, -1); + printf("["); + for (i = 1; i <= n; i++) { + printf("%d = ", i); + lua_rawgeti(L, -1, i); + if (lua_isstring(L, -1)) + printf("%s ", lua_tostring(L, -1)); + else + printf("%s ", lua_typename(L, lua_type(L, -1))); + lua_pop(L, 1); + } + printf("]\n"); + /* leave ktable at the stack */ +} + +/* }====================================================== */ + +#endif diff --git a/thirdparty/lpeg/lpprint.h b/thirdparty/lpeg/lpprint.h new file mode 100644 index 000000000..e8e04e872 --- /dev/null +++ b/thirdparty/lpeg/lpprint.h @@ -0,0 +1,32 @@ + +#if !defined(lpprint_h) +#define lpprint_h + + +#include "lptree.h" +#include "lpvm.h" + + +#if defined(LPEG_DEBUG) + +void printpatt (Instruction *p); +void printtree (TTree *tree, int ident); +void printktable (lua_State *L, int idx); +void printcharset (const byte *st); +void printcaplist (Capture *cap); +void printinst (const Instruction *op, const Instruction *p); + +#else + +#define printktable(L,idx) \ + luaL_error(L, "function only implemented in debug mode") +#define printtree(tree,i) \ + luaL_error(L, "function only implemented in debug mode") +#define printpatt(p) \ + luaL_error(L, "function only implemented in debug mode") + +#endif + + +#endif + diff --git a/thirdparty/lpeg/lptree.c b/thirdparty/lpeg/lptree.c new file mode 100644 index 000000000..475b0c36b --- /dev/null +++ b/thirdparty/lpeg/lptree.c @@ -0,0 +1,1399 @@ + +#include +#include +#include + + +#include "lua.h" +#include "lauxlib.h" + +#include "lptypes.h" +#include "lpcap.h" +#include "lpcode.h" +#include "lpprint.h" +#include "lptree.h" +#include "lpcset.h" + + +/* number of siblings for each tree */ +const byte numsiblings[] = { + 0, 0, 0, /* char, set, any */ + 0, 0, 0, /* true, false, utf-range */ + 1, /* acc */ + 2, 2, /* seq, choice */ + 1, 1, /* not, and */ + 0, 0, 2, 1, 1, /* call, opencall, rule, prerule, grammar */ + 1, /* behind */ + 1, 1 /* capture, runtime capture */ +}; + + +static TTree *newgrammar (lua_State *L, int arg); + + +/* +** returns a reasonable name for value at index 'idx' on the stack +*/ +static const char *val2str (lua_State *L, int idx) { + const char *k = lua_tostring(L, idx); + if (k != NULL) + return lua_pushfstring(L, "%s", k); + else + return lua_pushfstring(L, "(a %s)", luaL_typename(L, idx)); +} + + +/* +** Fix a TOpenCall into a TCall node, using table 'postable' to +** translate a key to its rule address in the tree. Raises an +** error if key does not exist. +*/ +static void fixonecall (lua_State *L, int postable, TTree *g, TTree *t) { + int n; + lua_rawgeti(L, -1, t->key); /* get rule's name */ + lua_gettable(L, postable); /* query name in position table */ + n = lua_tonumber(L, -1); /* get (absolute) position */ + lua_pop(L, 1); /* remove position */ + if (n == 0) { /* no position? */ + lua_rawgeti(L, -1, t->key); /* get rule's name again */ + luaL_error(L, "rule '%s' undefined in given grammar", val2str(L, -1)); + } + t->tag = TCall; + t->u.ps = n - (t - g); /* position relative to node */ + assert(sib2(t)->tag == TRule); + sib2(t)->key = t->key; /* fix rule's key */ +} + + +/* +** Transform left associative constructions into right +** associative ones, for sequence and choice; that is: +** (t11 + t12) + t2 => t11 + (t12 + t2) +** (t11 * t12) * t2 => t11 * (t12 * t2) +** (that is, Op (Op t11 t12) t2 => Op t11 (Op t12 t2)) +*/ +static void correctassociativity (TTree *tree) { + TTree *t1 = sib1(tree); + assert(tree->tag == TChoice || tree->tag == TSeq); + while (t1->tag == tree->tag) { + int n1size = tree->u.ps - 1; /* t1 == Op t11 t12 */ + int n11size = t1->u.ps - 1; + int n12size = n1size - n11size - 1; + memmove(sib1(tree), sib1(t1), n11size * sizeof(TTree)); /* move t11 */ + tree->u.ps = n11size + 1; + sib2(tree)->tag = tree->tag; + sib2(tree)->u.ps = n12size + 1; + } +} + + +/* +** Make final adjustments in a tree. Fix open calls in tree 't', +** making them refer to their respective rules or raising appropriate +** errors (if not inside a grammar). Correct associativity of associative +** constructions (making them right associative). Assume that tree's +** ktable is at the top of the stack (for error messages). +*/ +static void finalfix (lua_State *L, int postable, TTree *g, TTree *t) { + tailcall: + switch (t->tag) { + case TGrammar: /* subgrammars were already fixed */ + return; + case TOpenCall: { + if (g != NULL) /* inside a grammar? */ + fixonecall(L, postable, g, t); + else { /* open call outside grammar */ + lua_rawgeti(L, -1, t->key); + luaL_error(L, "rule '%s' used outside a grammar", val2str(L, -1)); + } + break; + } + case TSeq: case TChoice: + correctassociativity(t); + break; + } + switch (numsiblings[t->tag]) { + case 1: /* finalfix(L, postable, g, sib1(t)); */ + t = sib1(t); goto tailcall; + case 2: + finalfix(L, postable, g, sib1(t)); + t = sib2(t); goto tailcall; /* finalfix(L, postable, g, sib2(t)); */ + default: assert(numsiblings[t->tag] == 0); break; + } +} + + + +/* +** {=================================================================== +** KTable manipulation +** +** - The ktable of a pattern 'p' can be shared by other patterns that +** contain 'p' and no other constants. Because of this sharing, we +** should not add elements to a 'ktable' unless it was freshly created +** for the new pattern. +** +** - The maximum index in a ktable is USHRT_MAX, because trees and +** patterns use unsigned shorts to store those indices. +** ==================================================================== +*/ + +/* +** Create a new 'ktable' to the pattern at the top of the stack. +*/ +static void newktable (lua_State *L, int n) { + lua_createtable(L, n, 0); /* create a fresh table */ + lua_setuservalue(L, -2); /* set it as 'ktable' for pattern */ +} + + +/* +** Add element 'idx' to 'ktable' of pattern at the top of the stack; +** Return index of new element. +** If new element is nil, does not add it to table (as it would be +** useless) and returns 0, as ktable[0] is always nil. +*/ +static int addtoktable (lua_State *L, int idx) { + if (lua_isnil(L, idx)) /* nil value? */ + return 0; + else { + int n; + lua_getuservalue(L, -1); /* get ktable from pattern */ + n = lua_rawlen(L, -1); + if (n >= USHRT_MAX) + luaL_error(L, "too many Lua values in pattern"); + lua_pushvalue(L, idx); /* element to be added */ + lua_rawseti(L, -2, ++n); + lua_pop(L, 1); /* remove 'ktable' */ + return n; + } +} + + +/* +** Return the number of elements in the ktable at 'idx'. +** In Lua 5.2/5.3, default "environment" for patterns is nil, not +** a table. Treat it as an empty table. In Lua 5.1, assumes that +** the environment has no numeric indices (len == 0) +*/ +static int ktablelen (lua_State *L, int idx) { + if (!lua_istable(L, idx)) return 0; + else return lua_rawlen(L, idx); +} + + +/* +** Concatentate the contents of table 'idx1' into table 'idx2'. +** (Assume that both indices are negative.) +** Return the original length of table 'idx2' (or 0, if no +** element was added, as there is no need to correct any index). +*/ +static int concattable (lua_State *L, int idx1, int idx2) { + int i; + int n1 = ktablelen(L, idx1); + int n2 = ktablelen(L, idx2); + if (n1 + n2 > USHRT_MAX) + luaL_error(L, "too many Lua values in pattern"); + if (n1 == 0) return 0; /* nothing to correct */ + for (i = 1; i <= n1; i++) { + lua_rawgeti(L, idx1, i); + lua_rawseti(L, idx2 - 1, n2 + i); /* correct 'idx2' */ + } + return n2; +} + + +/* +** When joining 'ktables', constants from one of the subpatterns must +** be renumbered; 'correctkeys' corrects their indices (adding 'n' +** to each of them) +*/ +static void correctkeys (TTree *tree, int n) { + if (n == 0) return; /* no correction? */ + tailcall: + switch (tree->tag) { + case TOpenCall: case TCall: case TRunTime: case TRule: { + if (tree->key > 0) + tree->key += n; + break; + } + case TCapture: { + if (tree->key > 0 && tree->cap != Carg && tree->cap != Cnum) + tree->key += n; + break; + } + default: break; + } + switch (numsiblings[tree->tag]) { + case 1: /* correctkeys(sib1(tree), n); */ + tree = sib1(tree); goto tailcall; + case 2: + correctkeys(sib1(tree), n); + tree = sib2(tree); goto tailcall; /* correctkeys(sib2(tree), n); */ + default: assert(numsiblings[tree->tag] == 0); break; + } +} + + +/* +** Join the ktables from p1 and p2 the ktable for the new pattern at the +** top of the stack, reusing them when possible. +*/ +static void joinktables (lua_State *L, int p1, TTree *t2, int p2) { + int n1, n2; + lua_getuservalue(L, p1); /* get ktables */ + lua_getuservalue(L, p2); + n1 = ktablelen(L, -2); + n2 = ktablelen(L, -1); + if (n1 == 0 && n2 == 0) /* are both tables empty? */ + lua_pop(L, 2); /* nothing to be done; pop tables */ + else if (n2 == 0 || lp_equal(L, -2, -1)) { /* 2nd table empty or equal? */ + lua_pop(L, 1); /* pop 2nd table */ + lua_setuservalue(L, -2); /* set 1st ktable into new pattern */ + } + else if (n1 == 0) { /* first table is empty? */ + lua_setuservalue(L, -3); /* set 2nd table into new pattern */ + lua_pop(L, 1); /* pop 1st table */ + } + else { + lua_createtable(L, n1 + n2, 0); /* create ktable for new pattern */ + /* stack: new p; ktable p1; ktable p2; new ktable */ + concattable(L, -3, -1); /* from p1 into new ktable */ + concattable(L, -2, -1); /* from p2 into new ktable */ + lua_setuservalue(L, -4); /* new ktable becomes 'p' environment */ + lua_pop(L, 2); /* pop other ktables */ + correctkeys(t2, n1); /* correction for indices from p2 */ + } +} + + +/* +** copy 'ktable' of element 'idx' to new tree (on top of stack) +*/ +static void copyktable (lua_State *L, int idx) { + lua_getuservalue(L, idx); + lua_setuservalue(L, -2); +} + + +/* +** merge 'ktable' from 'stree' at stack index 'idx' into 'ktable' +** from tree at the top of the stack, and correct corresponding +** tree. +*/ +static void mergektable (lua_State *L, int idx, TTree *stree) { + int n; + lua_getuservalue(L, -1); /* get ktables */ + lua_getuservalue(L, idx); + n = concattable(L, -1, -2); + lua_pop(L, 2); /* remove both ktables */ + correctkeys(stree, n); +} + + +/* +** Create a new 'ktable' to the pattern at the top of the stack, adding +** all elements from pattern 'p' (if not 0) plus element 'idx' to it. +** Return index of new element. +*/ +static int addtonewktable (lua_State *L, int p, int idx) { + newktable(L, 1); + if (p) + mergektable(L, p, NULL); + return addtoktable(L, idx); +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Tree generation +** ======================================================= +*/ + +/* +** In 5.2, could use 'luaL_testudata'... +*/ +static int testpattern (lua_State *L, int idx) { + if (lua_touserdata(L, idx)) { /* value is a userdata? */ + if (lua_getmetatable(L, idx)) { /* does it have a metatable? */ + luaL_getmetatable(L, PATTERN_T); + if (lua_rawequal(L, -1, -2)) { /* does it have the correct mt? */ + lua_pop(L, 2); /* remove both metatables */ + return 1; + } + } + } + return 0; +} + + +static Pattern *getpattern (lua_State *L, int idx) { + return (Pattern *)luaL_checkudata(L, idx, PATTERN_T); +} + + +static int getsize (lua_State *L, int idx) { + return (lua_rawlen(L, idx) - offsetof(Pattern, tree)) / sizeof(TTree); +} + + +static TTree *gettree (lua_State *L, int idx, int *len) { + Pattern *p = getpattern(L, idx); + if (len) + *len = getsize(L, idx); + return p->tree; +} + + +/* +** create a pattern followed by a tree with 'len' nodes. Set its +** uservalue (the 'ktable') equal to its metatable. (It could be any +** empty sequence; the metatable is at hand here, so we use it.) +*/ +static TTree *newtree (lua_State *L, int len) { + size_t size = offsetof(Pattern, tree) + len * sizeof(TTree); + Pattern *p = (Pattern *)lua_newuserdata(L, size); + luaL_getmetatable(L, PATTERN_T); + lua_pushvalue(L, -1); + lua_setuservalue(L, -3); + lua_setmetatable(L, -2); + p->code = NULL; + return p->tree; +} + + +static TTree *newleaf (lua_State *L, int tag) { + TTree *tree = newtree(L, 1); + tree->tag = tag; + return tree; +} + + +/* +** Create a tree for a charset, optimizing for special cases: empty set, +** full set, and singleton set. +*/ +static TTree *newcharset (lua_State *L, byte *cs) { + charsetinfo info; + Opcode op = charsettype(cs, &info); + switch (op) { + case IFail: return newleaf(L, TFalse); /* empty set */ + case IAny: return newleaf(L, TAny); /* full set */ + case IChar: { /* singleton set */ + TTree *tree =newleaf(L, TChar); + tree->u.n = info.offset; + return tree; + } + default: { /* regular set */ + int i; + int bsize = /* tree size in bytes */ + (int)offsetof(TTree, u.set.bitmap) + info.size; + TTree *tree = newtree(L, bytes2slots(bsize)); + assert(op == ISet); + tree->tag = TSet; + tree->u.set.offset = info.offset; + tree->u.set.size = info.size; + tree->u.set.deflt = info.deflt; + for (i = 0; i < info.size; i++) { + assert(&treebuffer(tree)[i] < (byte*)tree + bsize); + treebuffer(tree)[i] = cs[info.offset + i]; + } + return tree; + } + } +} + + +/* +** Add to tree a sequence where first sibling is 'sib' (with size +** 'sibsize'); return position for second sibling. +*/ +static TTree *seqaux (TTree *tree, TTree *sib, int sibsize) { + tree->tag = TSeq; tree->u.ps = sibsize + 1; + memcpy(sib1(tree), sib, sibsize * sizeof(TTree)); + return sib2(tree); +} + + +/* +** Build a sequence of 'n' nodes, each with tag 'tag' and 'u.n' got +** from the array 's' (or 0 if array is NULL). (TSeq is binary, so it +** must build a sequence of sequence of sequence...) +*/ +static void fillseq (TTree *tree, int tag, int n, const char *s) { + int i; + for (i = 0; i < n - 1; i++) { /* initial n-1 copies of Seq tag; Seq ... */ + tree->tag = TSeq; tree->u.ps = 2; + sib1(tree)->tag = tag; + sib1(tree)->u.n = s ? (byte)s[i] : 0; + tree = sib2(tree); + } + tree->tag = tag; /* last one does not need TSeq */ + tree->u.n = s ? (byte)s[i] : 0; +} + + +/* +** Numbers as patterns: +** 0 == true (always match); n == TAny repeated 'n' times; +** -n == not (TAny repeated 'n' times) +*/ +static TTree *numtree (lua_State *L, int n) { + if (n == 0) + return newleaf(L, TTrue); + else { + TTree *tree, *nd; + if (n > 0) + tree = nd = newtree(L, 2 * n - 1); + else { /* negative: code it as !(-n) */ + n = -n; + tree = newtree(L, 2 * n); + tree->tag = TNot; + nd = sib1(tree); + } + fillseq(nd, TAny, n, NULL); /* sequence of 'n' any's */ + return tree; + } +} + + +/* +** Convert value at index 'idx' to a pattern +*/ +static TTree *getpatt (lua_State *L, int idx, int *len) { + TTree *tree; + switch (lua_type(L, idx)) { + case LUA_TSTRING: { + size_t slen; + const char *s = lua_tolstring(L, idx, &slen); /* get string */ + if (slen == 0) /* empty? */ + tree = newleaf(L, TTrue); /* always match */ + else { + tree = newtree(L, 2 * (slen - 1) + 1); + fillseq(tree, TChar, slen, s); /* sequence of 'slen' chars */ + } + break; + } + case LUA_TNUMBER: { + int n = lua_tointeger(L, idx); + tree = numtree(L, n); + break; + } + case LUA_TBOOLEAN: { + tree = (lua_toboolean(L, idx) ? newleaf(L, TTrue) : newleaf(L, TFalse)); + break; + } + case LUA_TTABLE: { + tree = newgrammar(L, idx); + break; + } + case LUA_TFUNCTION: { + tree = newtree(L, 2); + tree->tag = TRunTime; + tree->key = addtonewktable(L, 0, idx); + sib1(tree)->tag = TTrue; + break; + } + default: { + return gettree(L, idx, len); + } + } + lua_replace(L, idx); /* put new tree into 'idx' slot */ + if (len) + *len = getsize(L, idx); + return tree; +} + + +/* +** create a new tree, whith a new root and one sibling. +** Sibling must be on the Lua stack, at index 1. +*/ +static TTree *newroot1sib (lua_State *L, int tag) { + int s1; + TTree *tree1 = getpatt(L, 1, &s1); + TTree *tree = newtree(L, 1 + s1); /* create new tree */ + tree->tag = tag; + memcpy(sib1(tree), tree1, s1 * sizeof(TTree)); + copyktable(L, 1); + return tree; +} + + +/* +** create a new tree, whith a new root and 2 siblings. +** Siblings must be on the Lua stack, first one at index 1. +*/ +static TTree *newroot2sib (lua_State *L, int tag) { + int s1, s2; + TTree *tree1 = getpatt(L, 1, &s1); + TTree *tree2 = getpatt(L, 2, &s2); + TTree *tree = newtree(L, 1 + s1 + s2); /* create new tree */ + tree->tag = tag; + tree->u.ps = 1 + s1; + memcpy(sib1(tree), tree1, s1 * sizeof(TTree)); + memcpy(sib2(tree), tree2, s2 * sizeof(TTree)); + joinktables(L, 1, sib2(tree), 2); + return tree; +} + + +static int lp_P (lua_State *L) { + luaL_checkany(L, 1); + getpatt(L, 1, NULL); + lua_settop(L, 1); + return 1; +} + + +/* +** sequence operator; optimizations: +** false x => false, x true => x, true x => x +** (cannot do x . false => false because x may have runtime captures) +*/ +static int lp_seq (lua_State *L) { + TTree *tree1 = getpatt(L, 1, NULL); + TTree *tree2 = getpatt(L, 2, NULL); + if (tree1->tag == TFalse || tree2->tag == TTrue) + lua_pushvalue(L, 1); /* false . x == false, x . true = x */ + else if (tree1->tag == TTrue) + lua_pushvalue(L, 2); /* true . x = x */ + else + newroot2sib(L, TSeq); + return 1; +} + + +/* +** choice operator; optimizations: +** charset / charset => charset +** true / x => true, x / false => x, false / x => x +** (x / true is not equivalent to true) +*/ +static int lp_choice (lua_State *L) { + Charset st1, st2; + TTree *t1 = getpatt(L, 1, NULL); + TTree *t2 = getpatt(L, 2, NULL); + if (tocharset(t1, &st1) && tocharset(t2, &st2)) { + loopset(i, st1.cs[i] |= st2.cs[i]); + newcharset(L, st1.cs); + } + else if (nofail(t1) || t2->tag == TFalse) + lua_pushvalue(L, 1); /* true / x => true, x / false => x */ + else if (t1->tag == TFalse) + lua_pushvalue(L, 2); /* false / x => x */ + else + newroot2sib(L, TChoice); + return 1; +} + + +/* +** p^n +*/ +static int lp_star (lua_State *L) { + int size1; + int n = (int)luaL_checkinteger(L, 2); + TTree *tree1 = getpatt(L, 1, &size1); + if (n >= 0) { /* seq tree1 (seq tree1 ... (seq tree1 (rep tree1))) */ + TTree *tree = newtree(L, (n + 1) * (size1 + 1)); + if (nullable(tree1)) + luaL_error(L, "loop body may accept empty string"); + while (n--) /* repeat 'n' times */ + tree = seqaux(tree, tree1, size1); + tree->tag = TRep; + memcpy(sib1(tree), tree1, size1 * sizeof(TTree)); + } + else { /* choice (seq tree1 ... choice tree1 true ...) true */ + TTree *tree; + n = -n; + /* size = (choice + seq + tree1 + true) * n, but the last has no seq */ + tree = newtree(L, n * (size1 + 3) - 1); + for (; n > 1; n--) { /* repeat (n - 1) times */ + tree->tag = TChoice; tree->u.ps = n * (size1 + 3) - 2; + sib2(tree)->tag = TTrue; + tree = sib1(tree); + tree = seqaux(tree, tree1, size1); + } + tree->tag = TChoice; tree->u.ps = size1 + 1; + sib2(tree)->tag = TTrue; + memcpy(sib1(tree), tree1, size1 * sizeof(TTree)); + } + copyktable(L, 1); + return 1; +} + + +/* +** #p == &p +*/ +static int lp_and (lua_State *L) { + newroot1sib(L, TAnd); + return 1; +} + + +/* +** -p == !p +*/ +static int lp_not (lua_State *L) { + newroot1sib(L, TNot); + return 1; +} + + +/* +** [t1 - t2] == Seq (Not t2) t1 +** If t1 and t2 are charsets, make their difference. +*/ +static int lp_sub (lua_State *L) { + Charset st1, st2; + int s1, s2; + TTree *t1 = getpatt(L, 1, &s1); + TTree *t2 = getpatt(L, 2, &s2); + if (tocharset(t1, &st1) && tocharset(t2, &st2)) { + loopset(i, st1.cs[i] &= ~st2.cs[i]); + newcharset(L, st1.cs); + } + else { + TTree *tree = newtree(L, 2 + s1 + s2); + tree->tag = TSeq; /* sequence of... */ + tree->u.ps = 2 + s2; + sib1(tree)->tag = TNot; /* ...not... */ + memcpy(sib1(sib1(tree)), t2, s2 * sizeof(TTree)); /* ...t2 */ + memcpy(sib2(tree), t1, s1 * sizeof(TTree)); /* ... and t1 */ + joinktables(L, 1, sib1(tree), 2); + } + return 1; +} + + +static int lp_set (lua_State *L) { + size_t l; + const char *s = luaL_checklstring(L, 1, &l); + byte buff[CHARSETSIZE]; + clearset(buff); + while (l--) { + setchar(buff, (byte)(*s)); + s++; + } + newcharset(L, buff); + return 1; +} + + +static int lp_range (lua_State *L) { + int arg; + int top = lua_gettop(L); + byte buff[CHARSETSIZE]; + clearset(buff); + for (arg = 1; arg <= top; arg++) { + int c; + size_t l; + const char *r = luaL_checklstring(L, arg, &l); + luaL_argcheck(L, l == 2, arg, "range must have two characters"); + for (c = (byte)r[0]; c <= (byte)r[1]; c++) + setchar(buff, c); + } + newcharset(L, buff); + return 1; +} + + +/* +** Fills a tree node with basic information about the UTF-8 code point +** 'cpu': its value in 'n', its length in 'cap', and its first byte in +** 'key' +*/ +static void codeutftree (lua_State *L, TTree *t, lua_Unsigned cpu, int arg) { + int len, fb, cp; + cp = (int)cpu; + if (cp <= 0x7f) { /* one byte? */ + len = 1; + fb = cp; + } else if (cp <= 0x7ff) { + len = 2; + fb = 0xC0 | (cp >> 6); + } else if (cp <= 0xffff) { + len = 3; + fb = 0xE0 | (cp >> 12); + } + else { + luaL_argcheck(L, cpu <= 0x10ffffu, arg, "invalid code point"); + len = 4; + fb = 0xF0 | (cp >> 18); + } + t->u.n = cp; + t->cap = len; + t->key = fb; +} + + +static int lp_utfr (lua_State *L) { + lua_Unsigned from = (lua_Unsigned)luaL_checkinteger(L, 1); + lua_Unsigned to = (lua_Unsigned)luaL_checkinteger(L, 2); + luaL_argcheck(L, from <= to, 2, "empty range"); + if (to <= 0x7f) { /* ascii range? */ + uint f; + byte buff[CHARSETSIZE]; /* code it as a regular charset */ + clearset(buff); + for (f = (int)from; f <= to; f++) + setchar(buff, f); + newcharset(L, buff); + } + else { /* multi-byte utf-8 range */ + TTree *tree = newtree(L, 2); + tree->tag = TUTFR; + codeutftree(L, tree, from, 1); + sib1(tree)->tag = TXInfo; + codeutftree(L, sib1(tree), to, 2); + } + return 1; +} + + +/* +** Look-behind predicate +*/ +static int lp_behind (lua_State *L) { + TTree *tree; + TTree *tree1 = getpatt(L, 1, NULL); + int n = fixedlen(tree1); + luaL_argcheck(L, n >= 0, 1, "pattern may not have fixed length"); + luaL_argcheck(L, !hascaptures(tree1), 1, "pattern have captures"); + luaL_argcheck(L, n <= MAXBEHIND, 1, "pattern too long to look behind"); + tree = newroot1sib(L, TBehind); + tree->u.n = n; + return 1; +} + + +/* +** Create a non-terminal +*/ +static int lp_V (lua_State *L) { + TTree *tree = newleaf(L, TOpenCall); + luaL_argcheck(L, !lua_isnoneornil(L, 1), 1, "non-nil value expected"); + tree->key = addtonewktable(L, 0, 1); + return 1; +} + + +/* +** Create a tree for a non-empty capture, with a body and +** optionally with an associated Lua value (at index 'labelidx' in the +** stack) +*/ +static int capture_aux (lua_State *L, int cap, int labelidx) { + TTree *tree = newroot1sib(L, TCapture); + tree->cap = cap; + tree->key = (labelidx == 0) ? 0 : addtonewktable(L, 1, labelidx); + return 1; +} + + +/* +** Fill a tree with an empty capture, using an empty (TTrue) sibling. +** (The 'key' field must be filled by the caller to finish the tree.) +*/ +static TTree *auxemptycap (TTree *tree, int cap) { + tree->tag = TCapture; + tree->cap = cap; + sib1(tree)->tag = TTrue; + return tree; +} + + +/* +** Create a tree for an empty capture. +*/ +static TTree *newemptycap (lua_State *L, int cap, int key) { + TTree *tree = auxemptycap(newtree(L, 2), cap); + tree->key = key; + return tree; +} + + +/* +** Create a tree for an empty capture with an associated Lua value. +*/ +static TTree *newemptycapkey (lua_State *L, int cap, int idx) { + TTree *tree = auxemptycap(newtree(L, 2), cap); + tree->key = addtonewktable(L, 0, idx); + return tree; +} + + +/* +** Captures with syntax p / v +** (function capture, query capture, string capture, or number capture) +*/ +static int lp_divcapture (lua_State *L) { + switch (lua_type(L, 2)) { + case LUA_TFUNCTION: return capture_aux(L, Cfunction, 2); + case LUA_TTABLE: return capture_aux(L, Cquery, 2); + case LUA_TSTRING: return capture_aux(L, Cstring, 2); + case LUA_TNUMBER: { + int n = lua_tointeger(L, 2); + TTree *tree = newroot1sib(L, TCapture); + luaL_argcheck(L, 0 <= n && n <= SHRT_MAX, 1, "invalid number"); + tree->cap = Cnum; + tree->key = n; + return 1; + } + default: + return luaL_error(L, "unexpected %s as 2nd operand to LPeg '/'", + luaL_typename(L, 2)); + } +} + + +static int lp_acccapture (lua_State *L) { + return capture_aux(L, Cacc, 2); +} + + +static int lp_substcapture (lua_State *L) { + return capture_aux(L, Csubst, 0); +} + + +static int lp_tablecapture (lua_State *L) { + return capture_aux(L, Ctable, 0); +} + + +static int lp_groupcapture (lua_State *L) { + if (lua_isnoneornil(L, 2)) + return capture_aux(L, Cgroup, 0); + else + return capture_aux(L, Cgroup, 2); +} + + +static int lp_foldcapture (lua_State *L) { + luaL_checktype(L, 2, LUA_TFUNCTION); + return capture_aux(L, Cfold, 2); +} + + +static int lp_simplecapture (lua_State *L) { + return capture_aux(L, Csimple, 0); +} + + +static int lp_poscapture (lua_State *L) { + newemptycap(L, Cposition, 0); + return 1; +} + + +static int lp_argcapture (lua_State *L) { + int n = (int)luaL_checkinteger(L, 1); + luaL_argcheck(L, 0 < n && n <= SHRT_MAX, 1, "invalid argument index"); + newemptycap(L, Carg, n); + return 1; +} + + +static int lp_backref (lua_State *L) { + luaL_checkany(L, 1); + newemptycapkey(L, Cbackref, 1); + return 1; +} + + +/* +** Constant capture +*/ +static int lp_constcapture (lua_State *L) { + int i; + int n = lua_gettop(L); /* number of values */ + if (n == 0) /* no values? */ + newleaf(L, TTrue); /* no capture */ + else if (n == 1) + newemptycapkey(L, Cconst, 1); /* single constant capture */ + else { /* create a group capture with all values */ + TTree *tree = newtree(L, 1 + 3 * (n - 1) + 2); + newktable(L, n); /* create a 'ktable' for new tree */ + tree->tag = TCapture; + tree->cap = Cgroup; + tree->key = 0; + tree = sib1(tree); + for (i = 1; i <= n - 1; i++) { + tree->tag = TSeq; + tree->u.ps = 3; /* skip TCapture and its sibling */ + auxemptycap(sib1(tree), Cconst); + sib1(tree)->key = addtoktable(L, i); + tree = sib2(tree); + } + auxemptycap(tree, Cconst); + tree->key = addtoktable(L, i); + } + return 1; +} + + +static int lp_matchtime (lua_State *L) { + TTree *tree; + luaL_checktype(L, 2, LUA_TFUNCTION); + tree = newroot1sib(L, TRunTime); + tree->key = addtonewktable(L, 1, 2); + return 1; +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Grammar - Tree generation +** ======================================================= +*/ + +/* +** push on the stack the index and the pattern for the +** initial rule of grammar at index 'arg' in the stack; +** also add that index into position table. +*/ +static void getfirstrule (lua_State *L, int arg, int postab) { + lua_rawgeti(L, arg, 1); /* access first element */ + if (lua_isstring(L, -1)) { /* is it the name of initial rule? */ + lua_pushvalue(L, -1); /* duplicate it to use as key */ + lua_gettable(L, arg); /* get associated rule */ + } + else { + lua_pushinteger(L, 1); /* key for initial rule */ + lua_insert(L, -2); /* put it before rule */ + } + if (!testpattern(L, -1)) { /* initial rule not a pattern? */ + if (lua_isnil(L, -1)) + luaL_error(L, "grammar has no initial rule"); + else + luaL_error(L, "initial rule '%s' is not a pattern", lua_tostring(L, -2)); + } + lua_pushvalue(L, -2); /* push key */ + lua_pushinteger(L, 1); /* push rule position (after TGrammar) */ + lua_settable(L, postab); /* insert pair at position table */ +} + +/* +** traverse grammar at index 'arg', pushing all its keys and patterns +** into the stack. Create a new table (before all pairs key-pattern) to +** collect all keys and their associated positions in the final tree +** (the "position table"). +** Return the number of rules and (in 'totalsize') the total size +** for the new tree. +*/ +static int collectrules (lua_State *L, int arg, int *totalsize) { + int n = 1; /* to count number of rules */ + int postab = lua_gettop(L) + 1; /* index of position table */ + int size; /* accumulator for total size */ + lua_newtable(L); /* create position table */ + getfirstrule(L, arg, postab); + size = 3 + getsize(L, postab + 2); /* TGrammar + TRule + TXInfo + rule */ + lua_pushnil(L); /* prepare to traverse grammar table */ + while (lua_next(L, arg) != 0) { + if (lua_tonumber(L, -2) == 1 || + lp_equal(L, -2, postab + 1)) { /* initial rule? */ + lua_pop(L, 1); /* remove value (keep key for lua_next) */ + continue; + } + if (!testpattern(L, -1)) /* value is not a pattern? */ + luaL_error(L, "rule '%s' is not a pattern", val2str(L, -2)); + luaL_checkstack(L, LUA_MINSTACK, "grammar has too many rules"); + lua_pushvalue(L, -2); /* push key (to insert into position table) */ + lua_pushinteger(L, size); + lua_settable(L, postab); + size += 2 + getsize(L, -1); /* add 'TRule + TXInfo + rule' to size */ + lua_pushvalue(L, -2); /* push key (for next lua_next) */ + n++; + } + *totalsize = size + 1; /* space for 'TTrue' finishing list of rules */ + return n; +} + + +static void buildgrammar (lua_State *L, TTree *grammar, int frule, int n) { + int i; + TTree *nd = sib1(grammar); /* auxiliary pointer to traverse the tree */ + for (i = 0; i < n; i++) { /* add each rule into new tree */ + int ridx = frule + 2*i + 1; /* index of i-th rule */ + int rulesize; + TTree *rn = gettree(L, ridx, &rulesize); + TTree *pr = sib1(nd); /* points to rule's prerule */ + nd->tag = TRule; + nd->key = 0; /* will be fixed when rule is used */ + pr->tag = TXInfo; + pr->u.n = i; /* rule number */ + nd->u.ps = rulesize + 2; /* point to next rule */ + memcpy(sib1(pr), rn, rulesize * sizeof(TTree)); /* copy rule */ + mergektable(L, ridx, sib1(nd)); /* merge its ktable into new one */ + nd = sib2(nd); /* move to next rule */ + } + nd->tag = TTrue; /* finish list of rules */ +} + + +/* +** Check whether a tree has potential infinite loops +*/ +static int checkloops (TTree *tree) { + tailcall: + if (tree->tag == TRep && nullable(sib1(tree))) + return 1; + else if (tree->tag == TGrammar) + return 0; /* sub-grammars already checked */ + else { + switch (numsiblings[tree->tag]) { + case 1: /* return checkloops(sib1(tree)); */ + tree = sib1(tree); goto tailcall; + case 2: + if (checkloops(sib1(tree))) return 1; + /* else return checkloops(sib2(tree)); */ + tree = sib2(tree); goto tailcall; + default: assert(numsiblings[tree->tag] == 0); return 0; + } + } +} + + +/* +** Give appropriate error message for 'verifyrule'. If a rule appears +** twice in 'passed', there is path from it back to itself without +** advancing the subject. +*/ +static int verifyerror (lua_State *L, unsigned short *passed, int npassed) { + int i, j; + for (i = npassed - 1; i >= 0; i--) { /* search for a repetition */ + for (j = i - 1; j >= 0; j--) { + if (passed[i] == passed[j]) { + lua_rawgeti(L, -1, passed[i]); /* get rule's key */ + return luaL_error(L, "rule '%s' may be left recursive", val2str(L, -1)); + } + } + } + return luaL_error(L, "too many left calls in grammar"); +} + + +/* +** Check whether a rule can be left recursive; raise an error in that +** case; otherwise return 1 iff pattern is nullable. +** The return value is used to check sequences, where the second pattern +** is only relevant if the first is nullable. +** Parameter 'nb' works as an accumulator, to allow tail calls in +** choices. ('nb' true makes function returns true.) +** Parameter 'passed' is a list of already visited rules, 'npassed' +** counts the elements in 'passed'. +** Assume ktable at the top of the stack. +*/ +static int verifyrule (lua_State *L, TTree *tree, unsigned short *passed, + int npassed, int nb) { + tailcall: + switch (tree->tag) { + case TChar: case TSet: case TAny: + case TFalse: case TUTFR: + return nb; /* cannot pass from here */ + case TTrue: + case TBehind: /* look-behind cannot have calls */ + return 1; + case TNot: case TAnd: case TRep: + /* return verifyrule(L, sib1(tree), passed, npassed, 1); */ + tree = sib1(tree); nb = 1; goto tailcall; + case TCapture: case TRunTime: case TXInfo: + /* return verifyrule(L, sib1(tree), passed, npassed, nb); */ + tree = sib1(tree); goto tailcall; + case TCall: + /* return verifyrule(L, sib2(tree), passed, npassed, nb); */ + tree = sib2(tree); goto tailcall; + case TSeq: /* only check 2nd child if first is nb */ + if (!verifyrule(L, sib1(tree), passed, npassed, 0)) + return nb; + /* else return verifyrule(L, sib2(tree), passed, npassed, nb); */ + tree = sib2(tree); goto tailcall; + case TChoice: /* must check both children */ + nb = verifyrule(L, sib1(tree), passed, npassed, nb); + /* return verifyrule(L, sib2(tree), passed, npassed, nb); */ + tree = sib2(tree); goto tailcall; + case TRule: + if (npassed >= MAXRULES) /* too many steps? */ + return verifyerror(L, passed, npassed); /* error */ + else { + passed[npassed++] = tree->key; /* add rule to path */ + /* return verifyrule(L, sib1(tree), passed, npassed); */ + tree = sib1(tree); goto tailcall; + } + case TGrammar: + return nullable(tree); /* sub-grammar cannot be left recursive */ + default: assert(0); return 0; + } +} + + +static void verifygrammar (lua_State *L, TTree *grammar) { + unsigned short passed[MAXRULES]; + TTree *rule; + /* check left-recursive rules */ + for (rule = sib1(grammar); rule->tag == TRule; rule = sib2(rule)) { + if (rule->key == 0) continue; /* unused rule */ + verifyrule(L, sib1(rule), passed, 0, 0); + } + assert(rule->tag == TTrue); + /* check infinite loops inside rules */ + for (rule = sib1(grammar); rule->tag == TRule; rule = sib2(rule)) { + if (rule->key == 0) continue; /* unused rule */ + if (checkloops(sib1(rule))) { + lua_rawgeti(L, -1, rule->key); /* get rule's key */ + luaL_error(L, "empty loop in rule '%s'", val2str(L, -1)); + } + } + assert(rule->tag == TTrue); +} + + +/* +** Give a name for the initial rule if it is not referenced +*/ +static void initialrulename (lua_State *L, TTree *grammar, int frule) { + if (sib1(grammar)->key == 0) { /* initial rule is not referenced? */ + int n = lua_rawlen(L, -1) + 1; /* index for name */ + lua_pushvalue(L, frule); /* rule's name */ + lua_rawseti(L, -2, n); /* ktable was on the top of the stack */ + sib1(grammar)->key = n; + } +} + + +static TTree *newgrammar (lua_State *L, int arg) { + int treesize; + int frule = lua_gettop(L) + 2; /* position of first rule's key */ + int n = collectrules(L, arg, &treesize); + TTree *g = newtree(L, treesize); + luaL_argcheck(L, n <= MAXRULES, arg, "grammar has too many rules"); + g->tag = TGrammar; g->u.n = n; + lua_newtable(L); /* create 'ktable' */ + lua_setuservalue(L, -2); + buildgrammar(L, g, frule, n); + lua_getuservalue(L, -1); /* get 'ktable' for new tree */ + finalfix(L, frule - 1, g, sib1(g)); + initialrulename(L, g, frule); + verifygrammar(L, g); + lua_pop(L, 1); /* remove 'ktable' */ + lua_insert(L, -(n * 2 + 2)); /* move new table to proper position */ + lua_pop(L, n * 2 + 1); /* remove position table + rule pairs */ + return g; /* new table at the top of the stack */ +} + +/* }====================================================== */ + + +static Instruction *prepcompile (lua_State *L, Pattern *p, int idx) { + lua_getuservalue(L, idx); /* push 'ktable' (may be used by 'finalfix') */ + finalfix(L, 0, NULL, p->tree); + lua_pop(L, 1); /* remove 'ktable' */ + return compile(L, p, getsize(L, idx)); +} + + +static int lp_printtree (lua_State *L) { + TTree *tree = getpatt(L, 1, NULL); + int c = lua_toboolean(L, 2); + if (c) { + lua_getuservalue(L, 1); /* push 'ktable' (may be used by 'finalfix') */ + finalfix(L, 0, NULL, tree); + lua_pop(L, 1); /* remove 'ktable' */ + } + printktable(L, 1); + printtree(tree, 0); + return 0; +} + + +static int lp_printcode (lua_State *L) { + Pattern *p = getpattern(L, 1); + printktable(L, 1); + if (p->code == NULL) /* not compiled yet? */ + prepcompile(L, p, 1); + printpatt(p->code); + return 0; +} + + +/* +** Get the initial position for the match, interpreting negative +** values from the end of the subject +*/ +static size_t initposition (lua_State *L, size_t len) { + lua_Integer ii = luaL_optinteger(L, 3, 1); + if (ii > 0) { /* positive index? */ + if ((size_t)ii <= len) /* inside the string? */ + return (size_t)ii - 1; /* return it (corrected to 0-base) */ + else return len; /* crop at the end */ + } + else { /* negative index */ + if ((size_t)(-ii) <= len) /* inside the string? */ + return len - ((size_t)(-ii)); /* return position from the end */ + else return 0; /* crop at the beginning */ + } +} + + +/* +** Main match function +*/ +static int lp_match (lua_State *L) { + Capture capture[INITCAPSIZE]; + const char *r; + size_t l; + Pattern *p = (getpatt(L, 1, NULL), getpattern(L, 1)); + Instruction *code = (p->code != NULL) ? p->code : prepcompile(L, p, 1); + const char *s = luaL_checklstring(L, SUBJIDX, &l); + size_t i = initposition(L, l); + int ptop = lua_gettop(L); + luaL_argcheck(L, l < MAXINDT, SUBJIDX, "subject too long"); + lua_pushnil(L); /* initialize subscache */ + lua_pushlightuserdata(L, capture); /* initialize caplistidx */ + lua_getuservalue(L, 1); /* initialize ktableidx */ + r = match(L, s, s + i, s + l, code, capture, ptop); + if (r == NULL) { + lua_pushnil(L); + return 1; + } + return getcaptures(L, s, r, ptop); +} + + + +/* +** {====================================================== +** Library creation and functions not related to matching +** ======================================================= +*/ + +/* maximum limit for stack size */ +#define MAXLIM (INT_MAX / 100) + +static int lp_setmax (lua_State *L) { + lua_Integer lim = luaL_checkinteger(L, 1); + luaL_argcheck(L, 0 < lim && lim <= MAXLIM, 1, "out of range"); + lua_settop(L, 1); + lua_setfield(L, LUA_REGISTRYINDEX, MAXSTACKIDX); + return 0; +} + + +static int lp_type (lua_State *L) { + if (testpattern(L, 1)) + lua_pushliteral(L, "pattern"); + else + lua_pushnil(L); + return 1; +} + + +int lp_gc (lua_State *L) { + Pattern *p = getpattern(L, 1); + freecode(L, p); /* delete code block */ + return 0; +} + + +/* +** Create a charset representing a category of characters, given by +** the predicate 'catf'. +*/ +static void createcat (lua_State *L, const char *catname, int (catf) (int)) { + int c; + byte buff[CHARSETSIZE]; + clearset(buff); + for (c = 0; c <= UCHAR_MAX; c++) + if (catf(c)) setchar(buff, c); + newcharset(L, buff); + lua_setfield(L, -2, catname); +} + + +static int lp_locale (lua_State *L) { + if (lua_isnoneornil(L, 1)) { + lua_settop(L, 0); + lua_createtable(L, 0, 12); + } + else { + luaL_checktype(L, 1, LUA_TTABLE); + lua_settop(L, 1); + } + createcat(L, "alnum", isalnum); + createcat(L, "alpha", isalpha); + createcat(L, "cntrl", iscntrl); + createcat(L, "digit", isdigit); + createcat(L, "graph", isgraph); + createcat(L, "lower", islower); + createcat(L, "print", isprint); + createcat(L, "punct", ispunct); + createcat(L, "space", isspace); + createcat(L, "upper", isupper); + createcat(L, "xdigit", isxdigit); + return 1; +} + + +static struct luaL_Reg pattreg[] = { + {"ptree", lp_printtree}, + {"pcode", lp_printcode}, + {"match", lp_match}, + {"B", lp_behind}, + {"V", lp_V}, + {"C", lp_simplecapture}, + {"Cc", lp_constcapture}, + {"Cmt", lp_matchtime}, + {"Cb", lp_backref}, + {"Carg", lp_argcapture}, + {"Cp", lp_poscapture}, + {"Cs", lp_substcapture}, + {"Ct", lp_tablecapture}, + {"Cf", lp_foldcapture}, + {"Cg", lp_groupcapture}, + {"P", lp_P}, + {"S", lp_set}, + {"R", lp_range}, + {"utfR", lp_utfr}, + {"locale", lp_locale}, + {"version", NULL}, + {"setmaxstack", lp_setmax}, + {"type", lp_type}, + {NULL, NULL} +}; + + +static struct luaL_Reg metareg[] = { + {"__mul", lp_seq}, + {"__add", lp_choice}, + {"__pow", lp_star}, + {"__gc", lp_gc}, + {"__len", lp_and}, + {"__div", lp_divcapture}, + {"__mod", lp_acccapture}, + {"__unm", lp_not}, + {"__sub", lp_sub}, + {NULL, NULL} +}; + + +int luaopen_lpeg (lua_State *L); +int luaopen_lpeg (lua_State *L) { + luaL_newmetatable(L, PATTERN_T); + lua_pushnumber(L, MAXBACK); /* initialize maximum backtracking */ + lua_setfield(L, LUA_REGISTRYINDEX, MAXSTACKIDX); + luaL_setfuncs(L, metareg, 0); + luaL_newlib(L, pattreg); + lua_pushvalue(L, -1); + lua_setfield(L, -3, "__index"); + lua_pushliteral(L, "LPeg " VERSION); + lua_setfield(L, -2, "version"); + return 1; +} + +/* }====================================================== */ diff --git a/thirdparty/lpeg/lptree.h b/thirdparty/lpeg/lptree.h new file mode 100644 index 000000000..c7887412b --- /dev/null +++ b/thirdparty/lpeg/lptree.h @@ -0,0 +1,92 @@ + +#if !defined(lptree_h) +#define lptree_h + + +#include "lptypes.h" + + +/* +** types of trees +*/ +typedef enum TTag { + TChar = 0, /* 'n' = char */ + TSet, /* the set is encoded in 'u.set' and the next 'u.set.size' bytes */ + TAny, + TTrue, + TFalse, + TUTFR, /* range of UTF-8 codepoints; 'n' has initial codepoint; + 'cap' has length; 'key' has first byte; + extra info is similar for end codepoint */ + TRep, /* 'sib1'* */ + TSeq, /* 'sib1' 'sib2' */ + TChoice, /* 'sib1' / 'sib2' */ + TNot, /* !'sib1' */ + TAnd, /* &'sib1' */ + TCall, /* ktable[key] is rule's key; 'sib2' is rule being called */ + TOpenCall, /* ktable[key] is rule's key */ + TRule, /* ktable[key] is rule's key (but key == 0 for unused rules); + 'sib1' is rule's pattern pre-rule; 'sib2' is next rule; + extra info 'n' is rule's sequential number */ + TXInfo, /* extra info */ + TGrammar, /* 'sib1' is initial (and first) rule */ + TBehind, /* 'sib1' is pattern, 'n' is how much to go back */ + TCapture, /* captures: 'cap' is kind of capture (enum 'CapKind'); + ktable[key] is Lua value associated with capture; + 'sib1' is capture body */ + TRunTime /* run-time capture: 'key' is Lua function; + 'sib1' is capture body */ +} TTag; + + +/* +** Tree trees +** The first child of a tree (if there is one) is immediately after +** the tree. A reference to a second child (ps) is its position +** relative to the position of the tree itself. +*/ +typedef struct TTree { + byte tag; + byte cap; /* kind of capture (if it is a capture) */ + unsigned short key; /* key in ktable for Lua data (0 if no key) */ + union { + int ps; /* occasional second child */ + int n; /* occasional counter */ + struct { + byte offset; /* compact set offset (in bytes) */ + byte size; /* compact set size (in bytes) */ + byte deflt; /* default value */ + byte bitmap[1]; /* bitmap (open array) */ + } set; /* for compact sets */ + } u; +} TTree; + + +/* access to charset */ +#define treebuffer(t) ((t)->u.set.bitmap) + + +/* +** A complete pattern has its tree plus, if already compiled, +** its corresponding code +*/ +typedef struct Pattern { + union Instruction *code; + TTree tree[1]; +} Pattern; + + +/* number of children for each tree */ +extern const byte numsiblings[]; + +/* access to children */ +#define sib1(t) ((t) + 1) +#define sib2(t) ((t) + (t)->u.ps) + + + + + + +#endif + diff --git a/thirdparty/lpeg/lptypes.h b/thirdparty/lpeg/lptypes.h new file mode 100644 index 000000000..66a1b3b56 --- /dev/null +++ b/thirdparty/lpeg/lptypes.h @@ -0,0 +1,150 @@ +/* +** LPeg - PEG pattern matching for Lua +** Copyright 2007-2023, Lua.org & PUC-Rio (see 'LICENSE' for license) +** written by Roberto Ierusalimschy +*/ + +#if !defined(lptypes_h) +#define lptypes_h + + +#include +#include +#include + +#include "lua.h" + + +#define VERSION "1.1.0" + + +#define PATTERN_T "lpeg-pattern" +#define MAXSTACKIDX "lpeg-maxstack" + + +/* +** compatibility with Lua 5.1 +*/ +#if (LUA_VERSION_NUM == 501) + +#define lp_equal lua_equal + +#define lua_getuservalue lua_getfenv +#define lua_setuservalue lua_setfenv + +#define lua_rawlen lua_objlen + +#define luaL_setfuncs(L,f,n) luaL_register(L,NULL,f) +#define luaL_newlib(L,f) luaL_register(L,"lpeg",f) + +typedef size_t lua_Unsigned; + +#endif + + +#if !defined(lp_equal) +#define lp_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ) +#endif + + +/* default maximum size for call/backtrack stack */ +#if !defined(MAXBACK) +#define MAXBACK 400 +#endif + + +/* maximum number of rules in a grammar (limited by 'unsigned short') */ +#if !defined(MAXRULES) +#define MAXRULES 1000 +#endif + + + +/* initial size for capture's list */ +#define INITCAPSIZE 32 + + +/* index, on Lua stack, for subject */ +#define SUBJIDX 2 + +/* number of fixed arguments to 'match' (before capture arguments) */ +#define FIXEDARGS 3 + +/* index, on Lua stack, for capture list */ +#define caplistidx(ptop) ((ptop) + 2) + +/* index, on Lua stack, for pattern's ktable */ +#define ktableidx(ptop) ((ptop) + 3) + +/* index, on Lua stack, for backtracking stack */ +#define stackidx(ptop) ((ptop) + 4) + + + +typedef unsigned char byte; + +typedef unsigned int uint; + + +#define BITSPERCHAR 8 + +#define CHARSETSIZE ((UCHAR_MAX/BITSPERCHAR) + 1) + + + +typedef struct Charset { + byte cs[CHARSETSIZE]; +} Charset; + + + +#define loopset(v,b) { int v; for (v = 0; v < CHARSETSIZE; v++) {b;} } + +#define fillset(s,c) memset(s,c,CHARSETSIZE) +#define clearset(s) fillset(s,0) + +/* number of slots needed for 'n' bytes */ +#define bytes2slots(n) (((n) - 1u) / (uint)sizeof(TTree) + 1u) + +/* set 'b' bit in charset 'cs' */ +#define setchar(cs,b) ((cs)[(b) >> 3] |= (1 << ((b) & 7))) + + +/* +** in capture instructions, 'kind' of capture and its offset are +** packed in field 'aux', 4 bits for each +*/ +#define getkind(op) ((op)->i.aux1 & 0xF) +#define getoff(op) (((op)->i.aux1 >> 4) & 0xF) +#define joinkindoff(k,o) ((k) | ((o) << 4)) + +#define MAXOFF 0xF +#define MAXAUX 0xFF + + +/* maximum number of bytes to look behind */ +#define MAXBEHIND MAXAUX + + +/* maximum size (in elements) for a pattern */ +#define MAXPATTSIZE (SHRT_MAX - 10) + + +/* size (in instructions) for l bytes (l > 0) */ +#define instsize(l) ((int)(((l) + (uint)sizeof(Instruction) - 1u) \ + / (uint)sizeof(Instruction))) + + +/* size (in elements) for a ISet instruction */ +#define CHARSETINSTSIZE (1 + instsize(CHARSETSIZE)) + +/* size (in elements) for a IFunc instruction */ +#define funcinstsize(p) ((p)->i.aux + 2) + + + +#define testchar(st,c) ((((uint)(st)[((c) >> 3)]) >> ((c) & 7)) & 1) + + +#endif + diff --git a/thirdparty/lpeg/lpvm.c b/thirdparty/lpeg/lpvm.c new file mode 100644 index 000000000..0a2fde4f5 --- /dev/null +++ b/thirdparty/lpeg/lpvm.c @@ -0,0 +1,455 @@ + +#include +#include + + +#include "lua.h" +#include "lauxlib.h" + +#include "lpcap.h" +#include "lptypes.h" +#include "lpvm.h" +#include "lpprint.h" + + +/* initial size for call/backtrack stack */ +#if !defined(INITBACK) +#define INITBACK MAXBACK +#endif + + +#define getoffset(p) (((p) + 1)->offset) + +static const Instruction giveup = {{IGiveup, 0, {0}}}; + + +int charinset (const Instruction *i, const byte *buff, uint c) { + c -= i->i.aux2.set.offset; + if (c >= ((uint)i->i.aux2.set.size /* size in instructions... */ + * (uint)sizeof(Instruction) /* in bytes... */ + * 8u)) /* in bits */ + return i->i.aux1; /* out of range; return default value */ + return testchar(buff, c); +} + + +/* +** Decode one UTF-8 sequence, returning NULL if byte sequence is invalid. +*/ +static const char *utf8_decode (const char *o, int *val) { + static const uint limits[] = {0xFF, 0x7F, 0x7FF, 0xFFFFu}; + const unsigned char *s = (const unsigned char *)o; + uint c = s[0]; /* first byte */ + uint res = 0; /* final result */ + if (c < 0x80) /* ascii? */ + res = c; + else { + int count = 0; /* to count number of continuation bytes */ + while (c & 0x40) { /* still have continuation bytes? */ + int cc = s[++count]; /* read next byte */ + if ((cc & 0xC0) != 0x80) /* not a continuation byte? */ + return NULL; /* invalid byte sequence */ + res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */ + c <<= 1; /* to test next bit */ + } + res |= (c & 0x7F) << (count * 5); /* add first byte */ + if (count > 3 || res > 0x10FFFFu || res <= limits[count]) + return NULL; /* invalid byte sequence */ + s += count; /* skip continuation bytes read */ + } + *val = res; + return (const char *)s + 1; /* +1 to include first byte */ +} + + +/* +** {====================================================== +** Virtual Machine +** ======================================================= +*/ + + +typedef struct Stack { + const char *s; /* saved position (or NULL for calls) */ + const Instruction *p; /* next instruction */ + int caplevel; +} Stack; + + +#define getstackbase(L, ptop) ((Stack *)lua_touserdata(L, stackidx(ptop))) + + +/* +** Ensures the size of array 'capture' (with size '*capsize' and +** 'captop' elements being used) is enough to accomodate 'n' extra +** elements plus one. (Because several opcodes add stuff to the capture +** array, it is simpler to ensure the array always has at least one free +** slot upfront and check its size later.) +*/ + +/* new size in number of elements cannot overflow integers, and new + size in bytes cannot overflow size_t. */ +#define MAXNEWSIZE \ + (((size_t)INT_MAX) <= (~(size_t)0 / sizeof(Capture)) ? \ + ((size_t)INT_MAX) : (~(size_t)0 / sizeof(Capture))) + +static Capture *growcap (lua_State *L, Capture *capture, int *capsize, + int captop, int n, int ptop) { + if (*capsize - captop > n) + return capture; /* no need to grow array */ + else { /* must grow */ + Capture *newc; + uint newsize = captop + n + 1; /* minimum size needed */ + if (newsize < (MAXNEWSIZE / 3) * 2) + newsize += newsize / 2; /* 1.5 that size, if not too big */ + else if (newsize < (MAXNEWSIZE / 9) * 8) + newsize += newsize / 8; /* else, try 9/8 that size */ + else + luaL_error(L, "too many captures"); + newc = (Capture *)lua_newuserdata(L, newsize * sizeof(Capture)); + memcpy(newc, capture, captop * sizeof(Capture)); + *capsize = newsize; + lua_replace(L, caplistidx(ptop)); + return newc; + } +} + + +/* +** Double the size of the stack +*/ +static Stack *doublestack (lua_State *L, Stack **stacklimit, int ptop) { + Stack *stack = getstackbase(L, ptop); + Stack *newstack; + int n = *stacklimit - stack; /* current stack size */ + int max, newn; + lua_getfield(L, LUA_REGISTRYINDEX, MAXSTACKIDX); + max = lua_tointeger(L, -1); /* maximum allowed size */ + lua_pop(L, 1); + if (n >= max) /* already at maximum size? */ + luaL_error(L, "backtrack stack overflow (current limit is %d)", max); + newn = 2 * n; /* new size */ + if (newn > max) newn = max; + newstack = (Stack *)lua_newuserdata(L, newn * sizeof(Stack)); + memcpy(newstack, stack, n * sizeof(Stack)); + lua_replace(L, stackidx(ptop)); + *stacklimit = newstack + newn; + return newstack + n; /* return next position */ +} + + +/* +** Interpret the result of a dynamic capture: false -> fail; +** true -> keep current position; number -> next position. +** Return new subject position. 'fr' is stack index where +** is the result; 'curr' is current subject position; 'limit' +** is subject's size. +*/ +static int resdyncaptures (lua_State *L, int fr, int curr, int limit) { + lua_Integer res; + if (!lua_toboolean(L, fr)) { /* false value? */ + lua_settop(L, fr - 1); /* remove results */ + return -1; /* and fail */ + } + else if (lua_isboolean(L, fr)) /* true? */ + res = curr; /* keep current position */ + else { + res = lua_tointeger(L, fr) - 1; /* new position */ + if (res < curr || res > limit) + luaL_error(L, "invalid position returned by match-time capture"); + } + lua_remove(L, fr); /* remove first result (offset) */ + return res; +} + + +/* +** Add capture values returned by a dynamic capture to the list +** 'capture', nested inside a group. 'fd' indexes the first capture +** value, 'n' is the number of values (at least 1). The open group +** capture is already in 'capture', before the place for the new entries. +*/ +static void adddyncaptures (Index_t index, Capture *capture, int n, int fd) { + int i; + assert(capture[-1].kind == Cgroup && capture[-1].siz == 0); + capture[-1].idx = 0; /* make group capture an anonymous group */ + for (i = 0; i < n; i++) { /* add runtime captures */ + capture[i].kind = Cruntime; + capture[i].siz = 1; /* mark it as closed */ + capture[i].idx = fd + i; /* stack index of capture value */ + capture[i].index = index; + } + capture[n].kind = Cclose; /* close group */ + capture[n].siz = 1; + capture[n].index = index; +} + + +/* +** Remove dynamic captures from the Lua stack (called in case of failure) +*/ +static int removedyncap (lua_State *L, Capture *capture, + int level, int last) { + int id = finddyncap(capture + level, capture + last); /* index of 1st cap. */ + int top = lua_gettop(L); + if (id == 0) return 0; /* no dynamic captures? */ + lua_settop(L, id - 1); /* remove captures */ + return top - id + 1; /* number of values removed */ +} + + +/* +** Find the corresponding 'open' capture before 'cap', when that capture +** can become a full capture. If a full capture c1 is followed by an +** empty capture c2, there is no way to know whether c2 is inside +** c1. So, full captures can enclose only captures that start *before* +** its end. +*/ +static Capture *findopen (Capture *cap, Index_t currindex) { + int i; + cap--; /* check last capture */ + /* Must it be inside current one, but starts where current one ends? */ + if (!isopencap(cap) && cap->index == currindex) + return NULL; /* current one cannot be a full capture */ + /* else, look for an 'open' capture */ + for (i = 0; i < MAXLOP; i++, cap--) { + if (currindex - cap->index >= UCHAR_MAX) + return NULL; /* capture too long for a full capture */ + else if (isopencap(cap)) /* open capture? */ + return cap; /* that's the one to be closed */ + else if (cap->kind == Cclose) + return NULL; /* a full capture should not nest a non-full one */ + } + return NULL; /* not found within allowed search limit */ +} + + +/* +** Opcode interpreter +*/ +const char *match (lua_State *L, const char *o, const char *s, const char *e, + Instruction *op, Capture *capture, int ptop) { + Stack stackbase[INITBACK]; + Stack *stacklimit = stackbase + INITBACK; + Stack *stack = stackbase; /* point to first empty slot in stack */ + int capsize = INITCAPSIZE; + int captop = 0; /* point to first empty slot in captures */ + int ndyncap = 0; /* number of dynamic captures (in Lua stack) */ + const Instruction *p = op; /* current instruction */ + stack->p = &giveup; stack->s = s; stack->caplevel = 0; stack++; + lua_pushlightuserdata(L, stackbase); + for (;;) { +#if defined(DEBUG) + printf("-------------------------------------\n"); + printcaplist(capture, capture + captop); + printf("s: |%s| stck:%d, dyncaps:%d, caps:%d ", + s, (int)(stack - getstackbase(L, ptop)), ndyncap, captop); + printinst(op, p); +#endif + assert(stackidx(ptop) + ndyncap == lua_gettop(L) && ndyncap <= captop); + switch ((Opcode)p->i.code) { + case IEnd: { + assert(stack == getstackbase(L, ptop) + 1); + capture[captop].kind = Cclose; + capture[captop].index = MAXINDT; + return s; + } + case IGiveup: { + assert(stack == getstackbase(L, ptop)); + return NULL; + } + case IRet: { + assert(stack > getstackbase(L, ptop) && (stack - 1)->s == NULL); + p = (--stack)->p; + continue; + } + case IAny: { + if (s < e) { p++; s++; } + else goto fail; + continue; + } + case IUTFR: { + int codepoint; + if (s >= e) + goto fail; + s = utf8_decode (s, &codepoint); + if (s && p[1].offset <= codepoint && codepoint <= utf_to(p)) + p += 2; + else + goto fail; + continue; + } + case ITestAny: { + if (s < e) p += 2; + else p += getoffset(p); + continue; + } + case IChar: { + if ((byte)*s == p->i.aux1 && s < e) { p++; s++; } + else goto fail; + continue; + } + case ITestChar: { + if ((byte)*s == p->i.aux1 && s < e) p += 2; + else p += getoffset(p); + continue; + } + case ISet: { + uint c = (byte)*s; + if (charinset(p, (p+1)->buff, c) && s < e) + { p += 1 + p->i.aux2.set.size; s++; } + else goto fail; + continue; + } + case ITestSet: { + uint c = (byte)*s; + if (charinset(p, (p + 2)->buff, c) && s < e) + p += 2 + p->i.aux2.set.size; + else p += getoffset(p); + continue; + } + case IBehind: { + int n = p->i.aux1; + if (n > s - o) goto fail; + s -= n; p++; + continue; + } + case ISpan: { + for (; s < e; s++) { + uint c = (byte)*s; + if (!charinset(p, (p+1)->buff, c)) break; + } + p += 1 + p->i.aux2.set.size; + continue; + } + case IJmp: { + p += getoffset(p); + continue; + } + case IChoice: { + if (stack == stacklimit) + stack = doublestack(L, &stacklimit, ptop); + stack->p = p + getoffset(p); + stack->s = s; + stack->caplevel = captop; + stack++; + p += 2; + continue; + } + case ICall: { + if (stack == stacklimit) + stack = doublestack(L, &stacklimit, ptop); + stack->s = NULL; + stack->p = p + 2; /* save return address */ + stack++; + p += getoffset(p); + continue; + } + case ICommit: { + assert(stack > getstackbase(L, ptop) && (stack - 1)->s != NULL); + stack--; + p += getoffset(p); + continue; + } + case IPartialCommit: { + assert(stack > getstackbase(L, ptop) && (stack - 1)->s != NULL); + (stack - 1)->s = s; + (stack - 1)->caplevel = captop; + p += getoffset(p); + continue; + } + case IBackCommit: { + assert(stack > getstackbase(L, ptop) && (stack - 1)->s != NULL); + s = (--stack)->s; + if (ndyncap > 0) /* are there matchtime captures? */ + ndyncap -= removedyncap(L, capture, stack->caplevel, captop); + captop = stack->caplevel; + p += getoffset(p); + continue; + } + case IFailTwice: + assert(stack > getstackbase(L, ptop)); + stack--; + /* FALLTHROUGH */ + case IFail: + fail: { /* pattern failed: try to backtrack */ + do { /* remove pending calls */ + assert(stack > getstackbase(L, ptop)); + s = (--stack)->s; + } while (s == NULL); + if (ndyncap > 0) /* is there matchtime captures? */ + ndyncap -= removedyncap(L, capture, stack->caplevel, captop); + captop = stack->caplevel; + p = stack->p; +#if defined(DEBUG) + printf("**FAIL**\n"); +#endif + continue; + } + case ICloseRunTime: { + CapState cs; + int rem, res, n; + int fr = lua_gettop(L) + 1; /* stack index of first result */ + cs.reclevel = 0; cs.L = L; + cs.s = o; cs.ocap = capture; cs.ptop = ptop; + n = runtimecap(&cs, capture + captop, s, &rem); /* call function */ + captop -= n; /* remove nested captures */ + ndyncap -= rem; /* update number of dynamic captures */ + fr -= rem; /* 'rem' items were popped from Lua stack */ + res = resdyncaptures(L, fr, s - o, e - o); /* get result */ + if (res == -1) /* fail? */ + goto fail; + s = o + res; /* else update current position */ + n = lua_gettop(L) - fr + 1; /* number of new captures */ + ndyncap += n; /* update number of dynamic captures */ + if (n == 0) /* no new captures? */ + captop--; /* remove open group */ + else { /* new captures; keep original open group */ + if (fr + n >= SHRT_MAX) + luaL_error(L, "too many results in match-time capture"); + /* add new captures + close group to 'capture' list */ + capture = growcap(L, capture, &capsize, captop, n + 1, ptop); + adddyncaptures(s - o, capture + captop, n, fr); + captop += n + 1; /* new captures + close group */ + } + p++; + continue; + } + case ICloseCapture: { + Capture *open = findopen(capture + captop, s - o); + assert(captop > 0); + if (open) { /* if possible, turn capture into a full capture */ + open->siz = (s - o) - open->index + 1; + p++; + continue; + } + else { /* must create a close capture */ + capture[captop].siz = 1; /* mark entry as closed */ + capture[captop].index = s - o; + goto pushcapture; + } + } + case IOpenCapture: + capture[captop].siz = 0; /* mark entry as open */ + capture[captop].index = s - o; + goto pushcapture; + case IFullCapture: + capture[captop].siz = getoff(p) + 1; /* save capture size */ + capture[captop].index = s - o - getoff(p); + /* goto pushcapture; */ + pushcapture: { + capture[captop].idx = p->i.aux2.key; + capture[captop].kind = getkind(p); + captop++; + capture = growcap(L, capture, &capsize, captop, 0, ptop); + p++; + continue; + } + default: assert(0); return NULL; + } + } +} + +/* }====================================================== */ + + diff --git a/thirdparty/lpeg/lpvm.h b/thirdparty/lpeg/lpvm.h new file mode 100644 index 000000000..684f0c9a0 --- /dev/null +++ b/thirdparty/lpeg/lpvm.h @@ -0,0 +1,79 @@ + +#if !defined(lpvm_h) +#define lpvm_h + +#include "lpcap.h" + + +/* +** About Character sets in instructions: a set is a bit map with an +** initial offset, in bits, and a size, in number of instructions. +** aux1 has the default value for the bits outsize that range. +*/ + + +/* Virtual Machine's instructions */ +typedef enum Opcode { + IAny, /* if no char, fail */ + IChar, /* if char != aux1, fail */ + ISet, /* if char not in set, fail */ + ITestAny, /* in no char, jump to 'offset' */ + ITestChar, /* if char != aux1, jump to 'offset' */ + ITestSet, /* if char not in set, jump to 'offset' */ + ISpan, /* read a span of chars in set */ + IUTFR, /* if codepoint not in range [offset, utf_to], fail */ + IBehind, /* walk back 'aux1' characters (fail if not possible) */ + IRet, /* return from a rule */ + IEnd, /* end of pattern */ + IChoice, /* stack a choice; next fail will jump to 'offset' */ + IJmp, /* jump to 'offset' */ + ICall, /* call rule at 'offset' */ + IOpenCall, /* call rule number 'key' (must be closed to a ICall) */ + ICommit, /* pop choice and jump to 'offset' */ + IPartialCommit, /* update top choice to current position and jump */ + IBackCommit, /* backtrack like "fail" but jump to its own 'offset' */ + IFailTwice, /* pop one choice and then fail */ + IFail, /* go back to saved state on choice and jump to saved offset */ + IGiveup, /* internal use */ + IFullCapture, /* complete capture of last 'off' chars */ + IOpenCapture, /* start a capture */ + ICloseCapture, + ICloseRunTime, + IEmpty /* to fill empty slots left by optimizations */ +} Opcode; + + +/* +** All array of instructions has a 'codesize' as its first element +** and is referred by a pointer to its second element, which is the +** first actual opcode. +*/ +typedef union Instruction { + struct Inst { + byte code; + byte aux1; + union { + short key; + struct { + byte offset; + byte size; + } set; + } aux2; + } i; + int offset; + uint codesize; + byte buff[1]; +} Instruction; + + +/* extract 24-bit value from an instruction */ +#define utf_to(inst) (((inst)->i.aux2.key << 8) | (inst)->i.aux1) + + +int charinset (const Instruction *i, const byte *buff, uint c); +const char *match (lua_State *L, const char *o, const char *s, const char *e, + Instruction *op, Capture *capture, int ptop); + + +#endif + diff --git a/thirdparty/lrandom/lrandom.c b/thirdparty/lrandom/lrandom.c new file mode 100644 index 000000000..f983a155e --- /dev/null +++ b/thirdparty/lrandom/lrandom.c @@ -0,0 +1,131 @@ +/* +* lrandom.c +* random-number library for Lua 5.1 based on the Mersenne Twister +* Luiz Henrique de Figueiredo +* 17 Mar 2010 12:31:47 +* This code is hereby placed in the public domain. +*/ + +#include +#include + +#include "lua.h" +#include "lauxlib.h" + +#define MYNAME "random" +#define MYVERSION MYNAME " library for " LUA_VERSION " / Mar 2010" +#define MYTYPE MYNAME " handle" + +#define SEED 2009UL + +#include "random.c" + +/* Singe: 5.2 renamed luaL_reg to luaL_Reg. Only the spelling changed. */ +#if LUA_VERSION_NUM >= 502 +#define luaL_reg luaL_Reg +#endif + +static MT *Pget(lua_State *L, int i) +{ + return luaL_checkudata(L,i,MYTYPE); +} + +static MT *Pnew(lua_State *L) +{ + MT *c=lua_newuserdata(L,sizeof(MT)); + luaL_getmetatable(L,MYTYPE); + lua_setmetatable(L,-2); + return c; +} + +static int Lnew(lua_State *L) /** new([seed]) */ +{ + lua_Number seed=luaL_optnumber(L,1,SEED); + MT *c=Pnew(L); + init_genrand(c,seed); + return 1; +} + +static int Lclone(lua_State *L) /** clone(c) */ +{ + MT *c=Pget(L,1); + MT *d=Pnew(L); + *d=*c; + return 1; +} + +static int Lseed(lua_State *L) /** seed(c,[seed]) */ +{ + MT *c=Pget(L,1); + init_genrand(c,luaL_optnumber(L,2,SEED)); + lua_settop(L,1); + return 1; +} + +static int Lvalue(lua_State *L) /** value(c,[a,b]) */ +{ + MT *c=Pget(L,1); + double a,b,r=genrand_res53(c); + switch (lua_gettop(L)) + { + case 1: + lua_pushnumber(L,r); + return 1; + case 2: + a=1; + b=luaL_checknumber(L,2); + break; + default: + a=luaL_checknumber(L,2); + b=luaL_checknumber(L,3); + break; + } + if (a>b) { double t=a; a=b; b=t; } + a=ceil(a); + b=floor(b); + if (a>b) return 0; + r=a+floor(r*(b-a+1)); + lua_pushnumber(L,r); + return 1; +} + +static int Ltostring(lua_State *L) +{ + MT *c=Pget(L,1); + lua_pushfstring(L,"%s %p",MYTYPE,(void*)c); + return 1; +} + +static const luaL_reg R[] = +{ + { "__tostring", Ltostring }, /** __tostring(c) */ + { "clone", Lclone }, + { "new", Lnew }, + { "seed", Lseed }, + { "value", Lvalue }, + { NULL, NULL } +}; + +LUALIB_API int luaopen_random(lua_State *L) +{ + luaL_newmetatable(L,MYTYPE); +#if LUA_VERSION_NUM < 502 + lua_setglobal(L,MYNAME); + luaL_register(L,MYNAME,R); +#else + /* Singe: 5.2 dropped luaL_register. The metatable is the module table here, so the functions go + straight into it and the module system binds the name; nothing is written to the globals. */ + luaL_setfuncs(L,R,0); +#endif + lua_pushliteral(L,"version"); /** version */ + lua_pushliteral(L,MYVERSION); + lua_settable(L,-3); + lua_pushliteral(L,"__index"); + lua_pushvalue(L,-2); + lua_settable(L,-3); + lua_pushliteral(L,"__call"); /** __call(c) */ + lua_pushliteral(L,"value"); + lua_gettable(L,-3); + lua_settable(L,-3); + return 1; +} diff --git a/thirdparty/lrandom/random.c b/thirdparty/lrandom/random.c new file mode 100644 index 000000000..39beedf83 --- /dev/null +++ b/thirdparty/lrandom/random.c @@ -0,0 +1,128 @@ +/* +* random.c +* Mersenne Twister random number generator +* Luiz Henrique de Figueiredo +* 23 Sep 2008 22:57:32 +* slightly modified from mt19937ar.c available at +* http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/MT2002/emt19937ar.html +*/ + +/* + A C-program for MT19937, with initialization improved 2002/1/26. + Coded by Takuji Nishimura and Makoto Matsumoto. + + Before using, initialize the state by using init_genrand(seed) + or init_by_array(init_key, key_length). + + Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, + 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 names of its contributors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + 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 OWNER 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. + + + Any feedback is very welcome. + http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html + email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space) +*/ + +/* Period parameters */ +#define N 624 +#define M 397 +#define MATRIX_A 0x9908b0dfUL /* constant vector a */ +#define UPPER_MASK 0x80000000UL /* most significant w-r bits */ +#define LOWER_MASK 0x7fffffffUL /* least significant r bits */ + +typedef struct { + unsigned long v[N]; /* the array for the state vector */ + int i; +} MT; + +#define mt (o->v) +#define mti (o->i) + +/* initializes mt[N] with a seed */ +static void init_genrand(MT *o, unsigned long s) +{ + mt[0]= s & 0xffffffffUL; + for (mti=1; mti> 30)) + mti); + /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ + /* In the previous versions, MSBs of the seed affect */ + /* only MSBs of the array mt[]. */ + /* 2002/01/09 modified by Makoto Matsumoto */ + mt[mti] &= 0xffffffffUL; + /* for >32 bit machines */ + } +} + +/* generates a random number on [0,0xffffffff]-interval */ +static unsigned long genrand_int32(MT *o) +{ + unsigned long y; + static unsigned long mag01[2]={0x0UL, MATRIX_A}; + /* mag01[x] = x * MATRIX_A for x=0,1 */ + + if (mti >= N) { /* generate N words at one time */ + int kk; + + if (mti == N+1) /* if init_genrand() has not been called, */ + init_genrand(o,5489UL); /* a default initial seed is used */ + + for (kk=0;kk> 1) ^ mag01[y & 0x1UL]; + } + for (;kk> 1) ^ mag01[y & 0x1UL]; + } + y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK); + mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL]; + + mti = 0; + } + + y = mt[mti++]; + + /* Tempering */ + y ^= (y >> 11); + y ^= (y << 7) & 0x9d2c5680UL; + y ^= (y << 15) & 0xefc60000UL; + y ^= (y >> 18); + + return y; +} + +/* generates a random number on [0,1) with 53-bit resolution*/ +static double genrand_res53(MT *o) +{ + unsigned long a=genrand_int32(o)>>5, b=genrand_int32(o)>>6; + return(a*67108864.0+b)*(1.0/9007199254740992.0); +} +/* These real versions are due to Isaku Wada, 2002/01/09 added */ diff --git a/thirdparty/lua-cjson/LICENSE b/thirdparty/lua-cjson/LICENSE new file mode 100644 index 000000000..4883748ef --- /dev/null +++ b/thirdparty/lua-cjson/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2010-2012 Mark Pulford + +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. diff --git a/thirdparty/lua-cjson/fpconv.c b/thirdparty/lua-cjson/fpconv.c new file mode 100644 index 000000000..0e21748e1 --- /dev/null +++ b/thirdparty/lua-cjson/fpconv.c @@ -0,0 +1,205 @@ +/* fpconv - Floating point conversion routines + * + * Copyright (c) 2011-2012 Mark Pulford + * + * 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. + */ + +/* JSON uses a '.' decimal separator. strtod() / sprintf() under C libraries + * with locale support will break when the decimal separator is a comma. + * + * fpconv_* will around these issues with a translation buffer if required. + */ + +#include +#include +#include +#include + +#include "fpconv.h" + +/* Lua CJSON assumes the locale is the same for all threads within a + * process and doesn't change after initialisation. + * + * This avoids the need for per thread storage or expensive checks + * for call. */ +static char locale_decimal_point = '.'; + +/* In theory multibyte decimal_points are possible, but + * Lua CJSON only supports UTF-8 and known locales only have + * single byte decimal points ([.,]). + * + * localconv() may not be thread safe (=>crash), and nl_langinfo() is + * not supported on some platforms. Use sprintf() instead - if the + * locale does change, at least Lua CJSON won't crash. */ +static void fpconv_update_locale() +{ + char buf[8]; + + snprintf(buf, sizeof(buf), "%g", 0.5); + + /* Failing this test might imply the platform has a buggy dtoa + * implementation or wide characters */ + if (buf[0] != '0' || buf[2] != '5' || buf[3] != 0) { + fprintf(stderr, "Error: wide characters found or printf() bug."); + abort(); + } + + locale_decimal_point = buf[1]; +} + +/* Check for a valid number character: [-+0-9a-yA-Y.] + * Eg: -0.6e+5, infinity, 0xF0.F0pF0 + * + * Used to find the probable end of a number. It doesn't matter if + * invalid characters are counted - strtod() will find the valid + * number if it exists. The risk is that slightly more memory might + * be allocated before a parse error occurs. */ +static inline int valid_number_character(char ch) +{ + char lower_ch; + + if ('0' <= ch && ch <= '9') + return 1; + if (ch == '-' || ch == '+' || ch == '.') + return 1; + + /* Hex digits, exponent (e), base (p), "infinity",.. */ + lower_ch = ch | 0x20; + if ('a' <= lower_ch && lower_ch <= 'y') + return 1; + + return 0; +} + +/* Calculate the size of the buffer required for a strtod locale + * conversion. */ +static int strtod_buffer_size(const char *s) +{ + const char *p = s; + + while (valid_number_character(*p)) + p++; + + return p - s; +} + +/* Similar to strtod(), but must be passed the current locale's decimal point + * character. Guaranteed to be called at the start of any valid number in a string */ +double fpconv_strtod(const char *nptr, char **endptr) +{ + char localbuf[FPCONV_G_FMT_BUFSIZE]; + char *buf, *endbuf, *dp; + int buflen; + double value; + + /* System strtod() is fine when decimal point is '.' */ + if (locale_decimal_point == '.') + return strtod(nptr, endptr); + + buflen = strtod_buffer_size(nptr); + if (!buflen) { + /* No valid characters found, standard strtod() return */ + *endptr = (char *)nptr; + return 0; + } + + /* Duplicate number into buffer */ + if (buflen >= FPCONV_G_FMT_BUFSIZE) { + /* Handle unusually large numbers */ + buf = (char *)malloc(buflen + 1); + if (!buf) { + fprintf(stderr, "Out of memory"); + abort(); + } + } else { + /* This is the common case.. */ + buf = localbuf; + } + memcpy(buf, nptr, buflen); + buf[buflen] = 0; + + /* Update decimal point character if found */ + dp = strchr(buf, '.'); + if (dp) + *dp = locale_decimal_point; + + value = strtod(buf, &endbuf); + *endptr = (char *)&nptr[endbuf - buf]; + if (buflen >= FPCONV_G_FMT_BUFSIZE) + free(buf); + + return value; +} + +/* "fmt" must point to a buffer of at least 6 characters */ +static void set_number_format(char *fmt, int precision) +{ + int d1, d2, i; + + assert(1 <= precision && precision <= 14); + + /* Create printf format (%.14g) from precision */ + d1 = precision / 10; + d2 = precision % 10; + fmt[0] = '%'; + fmt[1] = '.'; + i = 2; + if (d1) { + fmt[i++] = '0' + d1; + } + fmt[i++] = '0' + d2; + fmt[i++] = 'g'; + fmt[i] = 0; +} + +/* Assumes there is always at least 32 characters available in the target buffer */ +int fpconv_g_fmt(char *str, double num, int precision) +{ + char buf[FPCONV_G_FMT_BUFSIZE]; + char fmt[6]; + int len; + char *b; + + set_number_format(fmt, precision); + + /* Pass through when decimal point character is dot. */ + if (locale_decimal_point == '.') + return snprintf(str, FPCONV_G_FMT_BUFSIZE, fmt, num); + + /* snprintf() to a buffer then translate for other decimal point characters */ + len = snprintf(buf, FPCONV_G_FMT_BUFSIZE, fmt, num); + + /* Copy into target location. Translate decimal point if required */ + b = buf; + do { + *str++ = (*b == locale_decimal_point ? '.' : *b); + } while(*b++); + + return len; +} + +void fpconv_init() +{ + fpconv_update_locale(); +} + +/* vi:ai et sw=4 ts=4: + */ diff --git a/thirdparty/lua-cjson/fpconv.h b/thirdparty/lua-cjson/fpconv.h new file mode 100644 index 000000000..7b0d0ee31 --- /dev/null +++ b/thirdparty/lua-cjson/fpconv.h @@ -0,0 +1,22 @@ +/* Lua CJSON floating point conversion routines */ + +/* Buffer required to store the largest string representation of a double. + * + * Longest double printed with %.14g is 21 characters long: + * -1.7976931348623e+308 */ +# define FPCONV_G_FMT_BUFSIZE 32 + +#ifdef USE_INTERNAL_FPCONV +static inline void fpconv_init() +{ + /* Do nothing - not required */ +} +#else +extern void fpconv_init(); +#endif + +extern int fpconv_g_fmt(char*, double, int); +extern double fpconv_strtod(const char*, char**); + +/* vi:ai et sw=4 ts=4: + */ diff --git a/thirdparty/lua-cjson/lua_cjson.c b/thirdparty/lua-cjson/lua_cjson.c new file mode 100644 index 000000000..5ccfe2437 --- /dev/null +++ b/thirdparty/lua-cjson/lua_cjson.c @@ -0,0 +1,1354 @@ +/* Lua CJSON - JSON support for Lua + * + * Copyright (c) 2010-2012 Mark Pulford + * + * 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. + */ + +/* Caveats: + * - JSON "null" values are represented as lightuserdata since Lua + * tables cannot contain "nil". Compare with cjson.null. + * - Invalid UTF-8 characters are not detected and will be passed + * untouched. If required, UTF-8 error checking should be done + * outside this library. + * - Javascript comments are not part of the JSON spec, and are not + * currently supported. + * + * Note: Decoding is slower than encoding. Lua spends significant + * time (30%) managing tables when parsing JSON since it is + * difficult to know object/array sizes ahead of time. + */ + +#include +#include +#include +#include +#include "lua.h" +#include "lauxlib.h" + +#include "strbuf.h" +#include "fpconv.h" + +#ifndef CJSON_MODNAME +#define CJSON_MODNAME "cjson" +#endif + +#ifndef CJSON_VERSION +#define CJSON_VERSION "2.1.0" +#endif + +/* Workaround for Solaris platforms missing isinf() */ +#if !defined(isinf) && (defined(USE_INTERNAL_ISINF) || defined(MISSING_ISINF)) +#define isinf(x) (!isnan(x) && isnan((x) - (x))) +#endif + +#define DEFAULT_SPARSE_CONVERT 0 +#define DEFAULT_SPARSE_RATIO 2 +#define DEFAULT_SPARSE_SAFE 10 +#define DEFAULT_ENCODE_MAX_DEPTH 1000 +#define DEFAULT_DECODE_MAX_DEPTH 1000 +#define DEFAULT_ENCODE_INVALID_NUMBERS 0 +#define DEFAULT_DECODE_INVALID_NUMBERS 1 +#define DEFAULT_ENCODE_KEEP_BUFFER 1 +#define DEFAULT_ENCODE_NUMBER_PRECISION 14 + +#ifdef DISABLE_INVALID_NUMBERS +#undef DEFAULT_DECODE_INVALID_NUMBERS +#define DEFAULT_DECODE_INVALID_NUMBERS 0 +#endif + +typedef enum { + T_OBJ_BEGIN, + T_OBJ_END, + T_ARR_BEGIN, + T_ARR_END, + T_STRING, + T_NUMBER, + T_BOOLEAN, + T_NULL, + T_COLON, + T_COMMA, + T_END, + T_WHITESPACE, + T_ERROR, + T_UNKNOWN +} json_token_type_t; + +static const char *json_token_type_name[] = { + "T_OBJ_BEGIN", + "T_OBJ_END", + "T_ARR_BEGIN", + "T_ARR_END", + "T_STRING", + "T_NUMBER", + "T_BOOLEAN", + "T_NULL", + "T_COLON", + "T_COMMA", + "T_END", + "T_WHITESPACE", + "T_ERROR", + "T_UNKNOWN", + NULL +}; + +typedef struct { + json_token_type_t ch2token[256]; + char escape2char[256]; /* Decoding */ + + /* encode_buf is only allocated and used when + * encode_keep_buffer is set */ + strbuf_t encode_buf; + + int encode_sparse_convert; + int encode_sparse_ratio; + int encode_sparse_safe; + int encode_max_depth; + int encode_invalid_numbers; /* 2 => Encode as "null" */ + int encode_number_precision; + int encode_keep_buffer; + + int decode_invalid_numbers; + int decode_max_depth; +} json_config_t; + +typedef struct { + const char *data; + const char *ptr; + strbuf_t *tmp; /* Temporary storage for strings */ + json_config_t *cfg; + int current_depth; +} json_parse_t; + +typedef struct { + json_token_type_t type; + int index; + union { + const char *string; + double number; + int boolean; + } value; + int string_len; +} json_token_t; + +static const char *char2escape[256] = { + "\\u0000", "\\u0001", "\\u0002", "\\u0003", + "\\u0004", "\\u0005", "\\u0006", "\\u0007", + "\\b", "\\t", "\\n", "\\u000b", + "\\f", "\\r", "\\u000e", "\\u000f", + "\\u0010", "\\u0011", "\\u0012", "\\u0013", + "\\u0014", "\\u0015", "\\u0016", "\\u0017", + "\\u0018", "\\u0019", "\\u001a", "\\u001b", + "\\u001c", "\\u001d", "\\u001e", "\\u001f", + NULL, NULL, "\\\"", NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\/", + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, "\\\\", NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\u007f", + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, +}; + +/* ===== CONFIGURATION ===== */ + +static json_config_t *json_fetch_config(lua_State *l) +{ + json_config_t *cfg; + + cfg = (json_config_t *)lua_touserdata(l, lua_upvalueindex(1)); + if (!cfg) + luaL_error(l, "BUG: Unable to fetch CJSON configuration"); + + return cfg; +} + +/* Ensure the correct number of arguments have been provided. + * Pad with nil to allow other functions to simply check arg[i] + * to find whether an argument was provided */ +static json_config_t *json_arg_init(lua_State *l, int args) +{ + luaL_argcheck(l, lua_gettop(l) <= args, args + 1, + "found too many arguments"); + + while (lua_gettop(l) < args) + lua_pushnil(l); + + return json_fetch_config(l); +} + +/* Process integer options for configuration functions */ +static int json_integer_option(lua_State *l, int optindex, int *setting, + int min, int max) +{ + char errmsg[64]; + int value; + + if (!lua_isnil(l, optindex)) { + value = luaL_checkinteger(l, optindex); + snprintf(errmsg, sizeof(errmsg), "expected integer between %d and %d", min, max); + luaL_argcheck(l, min <= value && value <= max, 1, errmsg); + *setting = value; + } + + lua_pushinteger(l, *setting); + + return 1; +} + +/* Process enumerated arguments for a configuration function */ +static int json_enum_option(lua_State *l, int optindex, int *setting, + const char **options, int bool_true) +{ + static const char *bool_options[] = { "off", "on", NULL }; + + if (!options) { + options = bool_options; + bool_true = 1; + } + + if (!lua_isnil(l, optindex)) { + if (bool_true && lua_isboolean(l, optindex)) + *setting = lua_toboolean(l, optindex) * bool_true; + else + *setting = luaL_checkoption(l, optindex, NULL, options); + } + + if (bool_true && (*setting == 0 || *setting == bool_true)) + lua_pushboolean(l, *setting); + else + lua_pushstring(l, options[*setting]); + + return 1; +} + +/* Configures handling of extremely sparse arrays: + * convert: Convert extremely sparse arrays into objects? Otherwise error. + * ratio: 0: always allow sparse; 1: never allow sparse; >1: use ratio + * safe: Always use an array when the max index <= safe */ +static int json_cfg_encode_sparse_array(lua_State *l) +{ + json_config_t *cfg = json_arg_init(l, 3); + + json_enum_option(l, 1, &cfg->encode_sparse_convert, NULL, 1); + json_integer_option(l, 2, &cfg->encode_sparse_ratio, 0, INT_MAX); + json_integer_option(l, 3, &cfg->encode_sparse_safe, 0, INT_MAX); + + return 3; +} + +/* Configures the maximum number of nested arrays/objects allowed when + * encoding */ +static int json_cfg_encode_max_depth(lua_State *l) +{ + json_config_t *cfg = json_arg_init(l, 1); + + return json_integer_option(l, 1, &cfg->encode_max_depth, 1, INT_MAX); +} + +/* Configures the maximum number of nested arrays/objects allowed when + * encoding */ +static int json_cfg_decode_max_depth(lua_State *l) +{ + json_config_t *cfg = json_arg_init(l, 1); + + return json_integer_option(l, 1, &cfg->decode_max_depth, 1, INT_MAX); +} + +/* Configures number precision when converting doubles to text */ +static int json_cfg_encode_number_precision(lua_State *l) +{ + json_config_t *cfg = json_arg_init(l, 1); + + return json_integer_option(l, 1, &cfg->encode_number_precision, 1, 14); +} + +/* Configures JSON encoding buffer persistence */ +static int json_cfg_encode_keep_buffer(lua_State *l) +{ + json_config_t *cfg = json_arg_init(l, 1); + int old_value; + + old_value = cfg->encode_keep_buffer; + + json_enum_option(l, 1, &cfg->encode_keep_buffer, NULL, 1); + + /* Init / free the buffer if the setting has changed */ + if (old_value ^ cfg->encode_keep_buffer) { + if (cfg->encode_keep_buffer) + strbuf_init(&cfg->encode_buf, 0); + else + strbuf_free(&cfg->encode_buf); + } + + return 1; +} + +#if defined(DISABLE_INVALID_NUMBERS) && !defined(USE_INTERNAL_FPCONV) +void json_verify_invalid_number_setting(lua_State *l, int *setting) +{ + if (*setting == 1) { + *setting = 0; + luaL_error(l, "Infinity, NaN, and/or hexadecimal numbers are not supported."); + } +} +#else +#define json_verify_invalid_number_setting(l, s) do { } while(0) +#endif + +static int json_cfg_encode_invalid_numbers(lua_State *l) +{ + static const char *options[] = { "off", "on", "null", NULL }; + json_config_t *cfg = json_arg_init(l, 1); + + json_enum_option(l, 1, &cfg->encode_invalid_numbers, options, 1); + + json_verify_invalid_number_setting(l, &cfg->encode_invalid_numbers); + + return 1; +} + +static int json_cfg_decode_invalid_numbers(lua_State *l) +{ + json_config_t *cfg = json_arg_init(l, 1); + + json_enum_option(l, 1, &cfg->decode_invalid_numbers, NULL, 1); + + json_verify_invalid_number_setting(l, &cfg->encode_invalid_numbers); + + return 1; +} + +static int json_destroy_config(lua_State *l) +{ + json_config_t *cfg; + + cfg = (json_config_t *)lua_touserdata(l, 1); + if (cfg) + strbuf_free(&cfg->encode_buf); + cfg = NULL; + + return 0; +} + +static void json_create_config(lua_State *l) +{ + json_config_t *cfg; + int i; + + cfg = (json_config_t *)lua_newuserdata(l, sizeof(*cfg)); + + /* Create GC method to clean up strbuf */ + lua_newtable(l); + lua_pushcfunction(l, json_destroy_config); + lua_setfield(l, -2, "__gc"); + lua_setmetatable(l, -2); + + cfg->encode_sparse_convert = DEFAULT_SPARSE_CONVERT; + cfg->encode_sparse_ratio = DEFAULT_SPARSE_RATIO; + cfg->encode_sparse_safe = DEFAULT_SPARSE_SAFE; + cfg->encode_max_depth = DEFAULT_ENCODE_MAX_DEPTH; + cfg->decode_max_depth = DEFAULT_DECODE_MAX_DEPTH; + cfg->encode_invalid_numbers = DEFAULT_ENCODE_INVALID_NUMBERS; + cfg->decode_invalid_numbers = DEFAULT_DECODE_INVALID_NUMBERS; + cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER; + cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION; + +#if DEFAULT_ENCODE_KEEP_BUFFER > 0 + strbuf_init(&cfg->encode_buf, 0); +#endif + + /* Decoding init */ + + /* Tag all characters as an error */ + for (i = 0; i < 256; i++) + cfg->ch2token[i] = T_ERROR; + + /* Set tokens that require no further processing */ + cfg->ch2token['{'] = T_OBJ_BEGIN; + cfg->ch2token['}'] = T_OBJ_END; + cfg->ch2token['['] = T_ARR_BEGIN; + cfg->ch2token[']'] = T_ARR_END; + cfg->ch2token[','] = T_COMMA; + cfg->ch2token[':'] = T_COLON; + cfg->ch2token['\0'] = T_END; + cfg->ch2token[' '] = T_WHITESPACE; + cfg->ch2token['\t'] = T_WHITESPACE; + cfg->ch2token['\n'] = T_WHITESPACE; + cfg->ch2token['\r'] = T_WHITESPACE; + + /* Update characters that require further processing */ + cfg->ch2token['f'] = T_UNKNOWN; /* false? */ + cfg->ch2token['i'] = T_UNKNOWN; /* inf, ininity? */ + cfg->ch2token['I'] = T_UNKNOWN; + cfg->ch2token['n'] = T_UNKNOWN; /* null, nan? */ + cfg->ch2token['N'] = T_UNKNOWN; + cfg->ch2token['t'] = T_UNKNOWN; /* true? */ + cfg->ch2token['"'] = T_UNKNOWN; /* string? */ + cfg->ch2token['+'] = T_UNKNOWN; /* number? */ + cfg->ch2token['-'] = T_UNKNOWN; + for (i = 0; i < 10; i++) + cfg->ch2token['0' + i] = T_UNKNOWN; + + /* Lookup table for parsing escape characters */ + for (i = 0; i < 256; i++) + cfg->escape2char[i] = 0; /* String error */ + cfg->escape2char['"'] = '"'; + cfg->escape2char['\\'] = '\\'; + cfg->escape2char['/'] = '/'; + cfg->escape2char['b'] = '\b'; + cfg->escape2char['t'] = '\t'; + cfg->escape2char['n'] = '\n'; + cfg->escape2char['f'] = '\f'; + cfg->escape2char['r'] = '\r'; + cfg->escape2char['u'] = 'u'; /* Unicode parsing required */ +} + +/* ===== ENCODING ===== */ + +static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *json, int lindex, + const char *reason) +{ + if (!cfg->encode_keep_buffer) + strbuf_free(json); + luaL_error(l, "Cannot serialise %s: %s", + lua_typename(l, lua_type(l, lindex)), reason); +} + +/* json_append_string args: + * - lua_State + * - JSON strbuf + * - String (Lua stack index) + * + * Returns nothing. Doesn't remove string from Lua stack */ +static void json_append_string(lua_State *l, strbuf_t *json, int lindex) +{ + const char *escstr; + const char *str; + size_t len; + size_t i; + + str = lua_tolstring(l, lindex, &len); + + /* Worst case is len * 6 (all unicode escapes). + * This buffer is reused constantly for small strings + * If there are any excess pages, they won't be hit anyway. + * This gains ~5% speedup. */ + strbuf_ensure_empty_length(json, len * 6 + 2); + + strbuf_append_char_unsafe(json, '\"'); + for (i = 0; i < len; i++) { + escstr = char2escape[(unsigned char)str[i]]; + if (escstr) + strbuf_append_string(json, escstr); + else + strbuf_append_char_unsafe(json, str[i]); + } + strbuf_append_char_unsafe(json, '\"'); +} + +/* Find the size of the array on the top of the Lua stack + * -1 object (not a pure array) + * >=0 elements in array + */ +static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json) +{ + double k; + int max; + int items; + + max = 0; + items = 0; + + lua_pushnil(l); + /* table, startkey */ + while (lua_next(l, -2) != 0) { + /* table, key, value */ + if (lua_type(l, -2) == LUA_TNUMBER && + (k = lua_tonumber(l, -2))) { + /* Integer >= 1 ? */ + if (floor(k) == k && k >= 1) { + if (k > max) + max = k; + items++; + lua_pop(l, 1); + continue; + } + } + + /* Must not be an array (non integer key) */ + lua_pop(l, 2); + return -1; + } + + /* Encode excessively sparse arrays as objects (if enabled) */ + if (cfg->encode_sparse_ratio > 0 && + max > items * cfg->encode_sparse_ratio && + max > cfg->encode_sparse_safe) { + if (!cfg->encode_sparse_convert) + json_encode_exception(l, cfg, json, -1, "excessively sparse array"); + + return -1; + } + + return max; +} + +static void json_check_encode_depth(lua_State *l, json_config_t *cfg, + int current_depth, strbuf_t *json) +{ + /* Ensure there are enough slots free to traverse a table (key, + * value) and push a string for a potential error message. + * + * Unlike "decode", the key and value are still on the stack when + * lua_checkstack() is called. Hence an extra slot for luaL_error() + * below is required just in case the next check to lua_checkstack() + * fails. + * + * While this won't cause a crash due to the EXTRA_STACK reserve + * slots, it would still be an improper use of the API. */ + if (current_depth <= cfg->encode_max_depth && lua_checkstack(l, 3)) + return; + + if (!cfg->encode_keep_buffer) + strbuf_free(json); + + luaL_error(l, "Cannot serialise, excessive nesting (%d)", + current_depth); +} + +static void json_append_data(lua_State *l, json_config_t *cfg, + int current_depth, strbuf_t *json); + +/* json_append_array args: + * - lua_State + * - JSON strbuf + * - Size of passwd Lua array (top of stack) */ +static void json_append_array(lua_State *l, json_config_t *cfg, int current_depth, + strbuf_t *json, int array_length) +{ + int comma, i; + + strbuf_append_char(json, '['); + + comma = 0; + for (i = 1; i <= array_length; i++) { + if (comma) + strbuf_append_char(json, ','); + else + comma = 1; + + lua_rawgeti(l, -1, i); + json_append_data(l, cfg, current_depth, json); + lua_pop(l, 1); + } + + strbuf_append_char(json, ']'); +} + +static void json_append_number(lua_State *l, json_config_t *cfg, + strbuf_t *json, int lindex) +{ + double num = lua_tonumber(l, lindex); + int len; + + if (cfg->encode_invalid_numbers == 0) { + /* Prevent encoding invalid numbers */ + if (isinf(num) || isnan(num)) + json_encode_exception(l, cfg, json, lindex, + "must not be NaN or Infinity"); + } else if (cfg->encode_invalid_numbers == 1) { + /* Encode NaN/Infinity separately to ensure Javascript compatible + * values are used. */ + if (isnan(num)) { + strbuf_append_mem(json, "NaN", 3); + return; + } + if (isinf(num)) { + if (num < 0) + strbuf_append_mem(json, "-Infinity", 9); + else + strbuf_append_mem(json, "Infinity", 8); + return; + } + } else { + /* Encode invalid numbers as "null" */ + if (isinf(num) || isnan(num)) { + strbuf_append_mem(json, "null", 4); + return; + } + } + + strbuf_ensure_empty_length(json, FPCONV_G_FMT_BUFSIZE); + len = fpconv_g_fmt(strbuf_empty_ptr(json), num, cfg->encode_number_precision); + strbuf_extend_length(json, len); +} + +static void json_append_object(lua_State *l, json_config_t *cfg, + int current_depth, strbuf_t *json) +{ + int comma, keytype; + + /* Object */ + strbuf_append_char(json, '{'); + + lua_pushnil(l); + /* table, startkey */ + comma = 0; + while (lua_next(l, -2) != 0) { + if (comma) + strbuf_append_char(json, ','); + else + comma = 1; + + /* table, key, value */ + keytype = lua_type(l, -2); + if (keytype == LUA_TNUMBER) { + strbuf_append_char(json, '"'); + json_append_number(l, cfg, json, -2); + strbuf_append_mem(json, "\":", 2); + } else if (keytype == LUA_TSTRING) { + json_append_string(l, json, -2); + strbuf_append_char(json, ':'); + } else { + json_encode_exception(l, cfg, json, -2, + "table key must be a number or string"); + /* never returns */ + } + + /* table, key, value */ + json_append_data(l, cfg, current_depth, json); + lua_pop(l, 1); + /* table, key */ + } + + strbuf_append_char(json, '}'); +} + +/* Serialise Lua data into JSON string. */ +static void json_append_data(lua_State *l, json_config_t *cfg, + int current_depth, strbuf_t *json) +{ + int len; + + switch (lua_type(l, -1)) { + case LUA_TSTRING: + json_append_string(l, json, -1); + break; + case LUA_TNUMBER: + json_append_number(l, cfg, json, -1); + break; + case LUA_TBOOLEAN: + if (lua_toboolean(l, -1)) + strbuf_append_mem(json, "true", 4); + else + strbuf_append_mem(json, "false", 5); + break; + case LUA_TTABLE: + current_depth++; + json_check_encode_depth(l, cfg, current_depth, json); + len = lua_array_length(l, cfg, json); + if (len > 0) + json_append_array(l, cfg, current_depth, json, len); + else + json_append_object(l, cfg, current_depth, json); + break; + case LUA_TNIL: + strbuf_append_mem(json, "null", 4); + break; + case LUA_TLIGHTUSERDATA: + if (lua_touserdata(l, -1) == NULL) { + strbuf_append_mem(json, "null", 4); + break; + } + default: + /* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD, + * and LUA_TLIGHTUSERDATA) cannot be serialised */ + json_encode_exception(l, cfg, json, -1, "type not supported"); + /* never returns */ + } +} + +static int json_encode(lua_State *l) +{ + json_config_t *cfg = json_fetch_config(l); + strbuf_t local_encode_buf; + strbuf_t *encode_buf; + char *json; + int len; + + luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument"); + + if (!cfg->encode_keep_buffer) { + /* Use private buffer */ + encode_buf = &local_encode_buf; + strbuf_init(encode_buf, 0); + } else { + /* Reuse existing buffer */ + encode_buf = &cfg->encode_buf; + strbuf_reset(encode_buf); + } + + json_append_data(l, cfg, 0, encode_buf); + json = strbuf_string(encode_buf, &len); + + lua_pushlstring(l, json, len); + + if (!cfg->encode_keep_buffer) + strbuf_free(encode_buf); + + return 1; +} + +/* ===== DECODING ===== */ + +static void json_process_value(lua_State *l, json_parse_t *json, + json_token_t *token); + +static int hexdigit2int(char hex) +{ + if ('0' <= hex && hex <= '9') + return hex - '0'; + + /* Force lowercase */ + hex |= 0x20; + if ('a' <= hex && hex <= 'f') + return 10 + hex - 'a'; + + return -1; +} + +static int decode_hex4(const char *hex) +{ + int digit[4]; + int i; + + /* Convert ASCII hex digit to numeric digit + * Note: this returns an error for invalid hex digits, including + * NULL */ + for (i = 0; i < 4; i++) { + digit[i] = hexdigit2int(hex[i]); + if (digit[i] < 0) { + return -1; + } + } + + return (digit[0] << 12) + + (digit[1] << 8) + + (digit[2] << 4) + + digit[3]; +} + +/* Converts a Unicode codepoint to UTF-8. + * Returns UTF-8 string length, and up to 4 bytes in *utf8 */ +static int codepoint_to_utf8(char *utf8, int codepoint) +{ + /* 0xxxxxxx */ + if (codepoint <= 0x7F) { + utf8[0] = codepoint; + return 1; + } + + /* 110xxxxx 10xxxxxx */ + if (codepoint <= 0x7FF) { + utf8[0] = (codepoint >> 6) | 0xC0; + utf8[1] = (codepoint & 0x3F) | 0x80; + return 2; + } + + /* 1110xxxx 10xxxxxx 10xxxxxx */ + if (codepoint <= 0xFFFF) { + utf8[0] = (codepoint >> 12) | 0xE0; + utf8[1] = ((codepoint >> 6) & 0x3F) | 0x80; + utf8[2] = (codepoint & 0x3F) | 0x80; + return 3; + } + + /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint <= 0x1FFFFF) { + utf8[0] = (codepoint >> 18) | 0xF0; + utf8[1] = ((codepoint >> 12) & 0x3F) | 0x80; + utf8[2] = ((codepoint >> 6) & 0x3F) | 0x80; + utf8[3] = (codepoint & 0x3F) | 0x80; + return 4; + } + + return 0; +} + + +/* Called when index pointing to beginning of UTF-16 code escape: \uXXXX + * \u is guaranteed to exist, but the remaining hex characters may be + * missing. + * Translate to UTF-8 and append to temporary token string. + * Must advance index to the next character to be processed. + * Returns: 0 success + * -1 error + */ +static int json_append_unicode_escape(json_parse_t *json) +{ + char utf8[4]; /* Surrogate pairs require 4 UTF-8 bytes */ + int codepoint; + int surrogate_low; + int len; + int escape_len = 6; + + /* Fetch UTF-16 code unit */ + codepoint = decode_hex4(json->ptr + 2); + if (codepoint < 0) + return -1; + + /* UTF-16 surrogate pairs take the following 2 byte form: + * 11011 x yyyyyyyyyy + * When x = 0: y is the high 10 bits of the codepoint + * x = 1: y is the low 10 bits of the codepoint + * + * Check for a surrogate pair (high or low) */ + if ((codepoint & 0xF800) == 0xD800) { + /* Error if the 1st surrogate is not high */ + if (codepoint & 0x400) + return -1; + + /* Ensure the next code is a unicode escape */ + if (*(json->ptr + escape_len) != '\\' || + *(json->ptr + escape_len + 1) != 'u') { + return -1; + } + + /* Fetch the next codepoint */ + surrogate_low = decode_hex4(json->ptr + 2 + escape_len); + if (surrogate_low < 0) + return -1; + + /* Error if the 2nd code is not a low surrogate */ + if ((surrogate_low & 0xFC00) != 0xDC00) + return -1; + + /* Calculate Unicode codepoint */ + codepoint = (codepoint & 0x3FF) << 10; + surrogate_low &= 0x3FF; + codepoint = (codepoint | surrogate_low) + 0x10000; + escape_len = 12; + } + + /* Convert codepoint to UTF-8 */ + len = codepoint_to_utf8(utf8, codepoint); + if (!len) + return -1; + + /* Append bytes and advance parse index */ + strbuf_append_mem_unsafe(json->tmp, utf8, len); + json->ptr += escape_len; + + return 0; +} + +static void json_set_token_error(json_token_t *token, json_parse_t *json, + const char *errtype) +{ + token->type = T_ERROR; + token->index = json->ptr - json->data; + token->value.string = errtype; +} + +static void json_next_string_token(json_parse_t *json, json_token_t *token) +{ + char *escape2char = json->cfg->escape2char; + char ch; + + /* Caller must ensure a string is next */ + assert(*json->ptr == '"'); + + /* Skip " */ + json->ptr++; + + /* json->tmp is the temporary strbuf used to accumulate the + * decoded string value. + * json->tmp is sized to handle JSON containing only a string value. + */ + strbuf_reset(json->tmp); + + while ((ch = *json->ptr) != '"') { + if (!ch) { + /* Premature end of the string */ + json_set_token_error(token, json, "unexpected end of string"); + return; + } + + /* Handle escapes */ + if (ch == '\\') { + /* Fetch escape character */ + ch = *(json->ptr + 1); + + /* Translate escape code and append to tmp string */ + ch = escape2char[(unsigned char)ch]; + if (ch == 'u') { + if (json_append_unicode_escape(json) == 0) + continue; + + json_set_token_error(token, json, + "invalid unicode escape code"); + return; + } + if (!ch) { + json_set_token_error(token, json, "invalid escape code"); + return; + } + + /* Skip '\' */ + json->ptr++; + } + /* Append normal character or translated single character + * Unicode escapes are handled above */ + strbuf_append_char_unsafe(json->tmp, ch); + json->ptr++; + } + json->ptr++; /* Eat final quote (") */ + + strbuf_ensure_null(json->tmp); + + token->type = T_STRING; + token->value.string = strbuf_string(json->tmp, &token->string_len); +} + +/* JSON numbers should take the following form: + * -?(0|[1-9]|[1-9][0-9]+)(.[0-9]+)?([eE][-+]?[0-9]+)? + * + * json_next_number_token() uses strtod() which allows other forms: + * - numbers starting with '+' + * - NaN, -NaN, infinity, -infinity + * - hexadecimal numbers + * - numbers with leading zeros + * + * json_is_invalid_number() detects "numbers" which may pass strtod()'s + * error checking, but should not be allowed with strict JSON. + * + * json_is_invalid_number() may pass numbers which cause strtod() + * to generate an error. + */ +static int json_is_invalid_number(json_parse_t *json) +{ + const char *p = json->ptr; + + /* Reject numbers starting with + */ + if (*p == '+') + return 1; + + /* Skip minus sign if it exists */ + if (*p == '-') + p++; + + /* Reject numbers starting with 0x, or leading zeros */ + if (*p == '0') { + int ch2 = *(p + 1); + + if ((ch2 | 0x20) == 'x' || /* Hex */ + ('0' <= ch2 && ch2 <= '9')) /* Leading zero */ + return 1; + + return 0; + } else if (*p <= '9') { + return 0; /* Ordinary number */ + } + + /* Reject inf/nan */ + if (!strncasecmp(p, "inf", 3)) + return 1; + if (!strncasecmp(p, "nan", 3)) + return 1; + + /* Pass all other numbers which may still be invalid, but + * strtod() will catch them. */ + return 0; +} + +static void json_next_number_token(json_parse_t *json, json_token_t *token) +{ + char *endptr; + + token->type = T_NUMBER; + token->value.number = fpconv_strtod(json->ptr, &endptr); + if (json->ptr == endptr) + json_set_token_error(token, json, "invalid number"); + else + json->ptr = endptr; /* Skip the processed number */ + + return; +} + +/* Fills in the token struct. + * T_STRING will return a pointer to the json_parse_t temporary string + * T_ERROR will leave the json->ptr pointer at the error. + */ +static void json_next_token(json_parse_t *json, json_token_t *token) +{ + const json_token_type_t *ch2token = json->cfg->ch2token; + int ch; + + /* Eat whitespace. */ + while (1) { + ch = (unsigned char)*(json->ptr); + token->type = ch2token[ch]; + if (token->type != T_WHITESPACE) + break; + json->ptr++; + } + + /* Store location of new token. Required when throwing errors + * for unexpected tokens (syntax errors). */ + token->index = json->ptr - json->data; + + /* Don't advance the pointer for an error or the end */ + if (token->type == T_ERROR) { + json_set_token_error(token, json, "invalid token"); + return; + } + + if (token->type == T_END) { + return; + } + + /* Found a known single character token, advance index and return */ + if (token->type != T_UNKNOWN) { + json->ptr++; + return; + } + + /* Process characters which triggered T_UNKNOWN + * + * Must use strncmp() to match the front of the JSON string. + * JSON identifier must be lowercase. + * When strict_numbers if disabled, either case is allowed for + * Infinity/NaN (since we are no longer following the spec..) */ + if (ch == '"') { + json_next_string_token(json, token); + return; + } else if (ch == '-' || ('0' <= ch && ch <= '9')) { + if (!json->cfg->decode_invalid_numbers && json_is_invalid_number(json)) { + json_set_token_error(token, json, "invalid number"); + return; + } + json_next_number_token(json, token); + return; + } else if (!strncmp(json->ptr, "true", 4)) { + token->type = T_BOOLEAN; + token->value.boolean = 1; + json->ptr += 4; + return; + } else if (!strncmp(json->ptr, "false", 5)) { + token->type = T_BOOLEAN; + token->value.boolean = 0; + json->ptr += 5; + return; + } else if (!strncmp(json->ptr, "null", 4)) { + token->type = T_NULL; + json->ptr += 4; + return; + } else if (json->cfg->decode_invalid_numbers && + json_is_invalid_number(json)) { + /* When decode_invalid_numbers is enabled, only attempt to process + * numbers we know are invalid JSON (Inf, NaN, hex) + * This is required to generate an appropriate token error, + * otherwise all bad tokens will register as "invalid number" + */ + json_next_number_token(json, token); + return; + } + + /* Token starts with t/f/n but isn't recognised above. */ + json_set_token_error(token, json, "invalid token"); +} + +/* This function does not return. + * DO NOT CALL WITH DYNAMIC MEMORY ALLOCATED. + * The only supported exception is the temporary parser string + * json->tmp struct. + * json and token should exist on the stack somewhere. + * luaL_error() will long_jmp and release the stack */ +static void json_throw_parse_error(lua_State *l, json_parse_t *json, + const char *exp, json_token_t *token) +{ + const char *found; + + strbuf_free(json->tmp); + + if (token->type == T_ERROR) + found = token->value.string; + else + found = json_token_type_name[token->type]; + + /* Note: token->index is 0 based, display starting from 1 */ + luaL_error(l, "Expected %s but found %s at character %d", + exp, found, token->index + 1); +} + +static inline void json_decode_ascend(json_parse_t *json) +{ + json->current_depth--; +} + +static void json_decode_descend(lua_State *l, json_parse_t *json, int slots) +{ + json->current_depth++; + + if (json->current_depth <= json->cfg->decode_max_depth && + lua_checkstack(l, slots)) { + return; + } + + strbuf_free(json->tmp); + luaL_error(l, "Found too many nested data structures (%d) at character %d", + json->current_depth, json->ptr - json->data); +} + +static void json_parse_object_context(lua_State *l, json_parse_t *json) +{ + json_token_t token; + + /* 3 slots required: + * .., table, key, value */ + json_decode_descend(l, json, 3); + + lua_newtable(l); + + json_next_token(json, &token); + + /* Handle empty objects */ + if (token.type == T_OBJ_END) { + json_decode_ascend(json); + return; + } + + while (1) { + if (token.type != T_STRING) + json_throw_parse_error(l, json, "object key string", &token); + + /* Push key */ + lua_pushlstring(l, token.value.string, token.string_len); + + json_next_token(json, &token); + if (token.type != T_COLON) + json_throw_parse_error(l, json, "colon", &token); + + /* Fetch value */ + json_next_token(json, &token); + json_process_value(l, json, &token); + + /* Set key = value */ + lua_rawset(l, -3); + + json_next_token(json, &token); + + if (token.type == T_OBJ_END) { + json_decode_ascend(json); + return; + } + + if (token.type != T_COMMA) + json_throw_parse_error(l, json, "comma or object end", &token); + + json_next_token(json, &token); + } +} + +/* Handle the array context */ +static void json_parse_array_context(lua_State *l, json_parse_t *json) +{ + json_token_t token; + int i; + + /* 2 slots required: + * .., table, value */ + json_decode_descend(l, json, 2); + + lua_newtable(l); + + json_next_token(json, &token); + + /* Handle empty arrays */ + if (token.type == T_ARR_END) { + json_decode_ascend(json); + return; + } + + for (i = 1; ; i++) { + json_process_value(l, json, &token); + lua_rawseti(l, -2, i); /* arr[i] = value */ + + json_next_token(json, &token); + + if (token.type == T_ARR_END) { + json_decode_ascend(json); + return; + } + + if (token.type != T_COMMA) + json_throw_parse_error(l, json, "comma or array end", &token); + + json_next_token(json, &token); + } +} + +/* Handle the "value" context */ +static void json_process_value(lua_State *l, json_parse_t *json, + json_token_t *token) +{ + switch (token->type) { + case T_STRING: + lua_pushlstring(l, token->value.string, token->string_len); + break;; + case T_NUMBER: + lua_pushnumber(l, token->value.number); + break;; + case T_BOOLEAN: + lua_pushboolean(l, token->value.boolean); + break;; + case T_OBJ_BEGIN: + json_parse_object_context(l, json); + break;; + case T_ARR_BEGIN: + json_parse_array_context(l, json); + break;; + case T_NULL: + /* In Lua, setting "t[k] = nil" will delete k from the table. + * Hence a NULL pointer lightuserdata object is used instead */ + lua_pushlightuserdata(l, NULL); + break;; + default: + json_throw_parse_error(l, json, "value", token); + } +} + +static int json_decode(lua_State *l) +{ + json_parse_t json; + json_token_t token; + size_t json_len; + + luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument"); + + json.cfg = json_fetch_config(l); + json.data = luaL_checklstring(l, 1, &json_len); + json.current_depth = 0; + json.ptr = json.data; + + /* Detect Unicode other than UTF-8 (see RFC 4627, Sec 3) + * + * CJSON can support any simple data type, hence only the first + * character is guaranteed to be ASCII (at worst: '"'). This is + * still enough to detect whether the wrong encoding is in use. */ + if (json_len >= 2 && (!json.data[0] || !json.data[1])) + luaL_error(l, "JSON parser does not support UTF-16 or UTF-32"); + + /* Ensure the temporary buffer can hold the entire string. + * This means we no longer need to do length checks since the decoded + * string must be smaller than the entire json string */ + json.tmp = strbuf_new(json_len); + + json_next_token(&json, &token); + json_process_value(l, &json, &token); + + /* Ensure there is no more input left */ + json_next_token(&json, &token); + + if (token.type != T_END) + json_throw_parse_error(l, &json, "the end", &token); + + strbuf_free(json.tmp); + + return 1; +} + +/* ===== INITIALISATION ===== */ + +/* Singe: 5.2 and later provide luaL_setfuncs, so this stand-in is only for 5.1. */ +#if LUA_VERSION_NUM < 502 +static void luaL_setfuncs (lua_State *l, const luaL_Reg *reg, int nup) +{ + int i; + + luaL_checkstack(l, nup, "too many upvalues"); + for (; reg->name != NULL; reg++) { + for (i = 0; i < nup; i++) + lua_pushvalue(l, -nup); + lua_pushcclosure(l, reg->func, nup); + lua_setfield(l, -(nup + 2), reg->name); + } + lua_pop(l, nup); +} +#endif + +static void set_info (lua_State *l) +{ + lua_pushliteral (l, CJSON_MODNAME); + lua_setfield(l, -2, "_NAME"); + lua_pushliteral(l, CJSON_VERSION); + lua_setfield(l, -2, "_VERSION"); +} + +static const struct luaL_Reg json_funcs[] = { + { "encode", json_encode }, + { "decode", json_decode }, + { "encode_sparse_array", json_cfg_encode_sparse_array }, + { "encode_max_depth", json_cfg_encode_max_depth }, + { "decode_max_depth", json_cfg_decode_max_depth }, + { "encode_number_precision", json_cfg_encode_number_precision }, + { "encode_keep_buffer", json_cfg_encode_keep_buffer }, + { "encode_invalid_numbers", json_cfg_encode_invalid_numbers }, + { "decode_invalid_numbers", json_cfg_decode_invalid_numbers }, + { NULL, NULL } +}; + +LUALIB_API int luaopen_cjson(lua_State *l) +{ + fpconv_init(); + lua_newtable(l); +#if LUA_VERSION_NUM < 502 + luaL_register(l, CJSON_MODNAME, json_funcs); +#endif + /* Singe: on 5.2 and later the luaL_setfuncs below fills the table on its own. */ + json_create_config(l); + luaL_setfuncs(l, json_funcs, 1); + set_info(l); + return 1; +} + +/* vi:ai et sw=4 ts=4: + */ diff --git a/thirdparty/lua-cjson/strbuf.c b/thirdparty/lua-cjson/strbuf.c new file mode 100644 index 000000000..61f47e164 --- /dev/null +++ b/thirdparty/lua-cjson/strbuf.c @@ -0,0 +1,252 @@ +/* strbuf - String buffer routines + * + * Copyright (c) 2010-2012 Mark Pulford + * + * 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. + */ + +#include +#include +#include +#include + +#include "strbuf.h" + +static void die(const char *fmt, ...) +{ + va_list arg; + + va_start(arg, fmt); + vfprintf(stderr, fmt, arg); + va_end(arg); + fprintf(stderr, "\n"); + + exit(-1); +} + +void strbuf_init(strbuf_t *s, int len) +{ + int size; + + if (len <= 0) + size = STRBUF_DEFAULT_SIZE; + else + size = len + 1; /* \0 terminator */ + + s->buf = NULL; + s->size = size; + s->length = 0; + s->increment = STRBUF_DEFAULT_INCREMENT; + s->dynamic = 0; + s->reallocs = 0; + s->debug = 0; + + s->buf = (char *)malloc(size); + if (!s->buf) + die("Out of memory"); + + strbuf_ensure_null(s); +} + +strbuf_t *strbuf_new(int len) +{ + strbuf_t *s; + + s = (strbuf_t*)malloc(sizeof(strbuf_t)); + if (!s) + die("Out of memory"); + + strbuf_init(s, len); + + /* Dynamic strbuf allocation / deallocation */ + s->dynamic = 1; + + return s; +} + +void strbuf_set_increment(strbuf_t *s, int increment) +{ + /* Increment > 0: Linear buffer growth rate + * Increment < -1: Exponential buffer growth rate */ + if (increment == 0 || increment == -1) + die("BUG: Invalid string increment"); + + s->increment = increment; +} + +static inline void debug_stats(strbuf_t *s) +{ + if (s->debug) { + fprintf(stderr, "strbuf(%p) reallocs: %d, length: %d, size: %d\n", + (void *)s, s->reallocs, s->length, s->size); + } +} + +/* If strbuf_t has not been dynamically allocated, strbuf_free() can + * be called any number of times strbuf_init() */ +void strbuf_free(strbuf_t *s) +{ + debug_stats(s); + + if (s->buf) { + free(s->buf); + s->buf = NULL; + } + if (s->dynamic) + free(s); +} + +char *strbuf_free_to_string(strbuf_t *s, int *len) +{ + char *buf; + + debug_stats(s); + + strbuf_ensure_null(s); + + buf = s->buf; + if (len) + *len = s->length; + + if (s->dynamic) + free(s); + + return buf; +} + +static int calculate_new_size(strbuf_t *s, int len) +{ + int reqsize, newsize; + + if (len <= 0) + die("BUG: Invalid strbuf length requested"); + + /* Ensure there is room for optional NULL termination */ + reqsize = len + 1; + + /* If the user has requested to shrink the buffer, do it exactly */ + if (s->size > reqsize) + return reqsize; + + newsize = s->size; + if (s->increment < 0) { + /* Exponential sizing */ + while (newsize < reqsize) + newsize *= -s->increment; + } else { + /* Linear sizing */ + newsize = ((newsize + s->increment - 1) / s->increment) * s->increment; + } + + return newsize; +} + + +/* Ensure strbuf can handle a string length bytes long (ignoring NULL + * optional termination). */ +void strbuf_resize(strbuf_t *s, int len) +{ + int newsize; + + newsize = calculate_new_size(s, len); + + if (s->debug > 1) { + fprintf(stderr, "strbuf(%p) resize: %d => %d\n", + (void *)s, s->size, newsize); + } + + s->size = newsize; + s->buf = (char *)realloc(s->buf, s->size); + if (!s->buf) + die("Out of memory"); + s->reallocs++; +} + +void strbuf_append_string(strbuf_t *s, const char *str) +{ + int space, i; + + space = strbuf_empty_length(s); + + for (i = 0; str[i]; i++) { + if (space < 1) { + strbuf_resize(s, s->length + 1); + space = strbuf_empty_length(s); + } + + s->buf[s->length] = str[i]; + s->length++; + space--; + } +} + +/* strbuf_append_fmt() should only be used when an upper bound + * is known for the output string. */ +void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...) +{ + va_list arg; + int fmt_len; + + strbuf_ensure_empty_length(s, len); + + va_start(arg, fmt); + fmt_len = vsnprintf(s->buf + s->length, len, fmt, arg); + va_end(arg); + + if (fmt_len < 0) + die("BUG: Unable to convert number"); /* This should never happen.. */ + + s->length += fmt_len; +} + +/* strbuf_append_fmt_retry() can be used when the there is no known + * upper bound for the output string. */ +void strbuf_append_fmt_retry(strbuf_t *s, const char *fmt, ...) +{ + va_list arg; + int fmt_len; + int empty_len; + int t; + + /* If the first attempt to append fails, resize the buffer appropriately + * and try again */ + for (t = 0; ; t++) { + va_start(arg, fmt); + /* Append the new formatted string */ + /* fmt_len is the length of the string required, excluding the + * trailing NULL */ + empty_len = strbuf_empty_length(s); + /* Add 1 since there is also space to store the terminating NULL. */ + fmt_len = vsnprintf(s->buf + s->length, empty_len + 1, fmt, arg); + va_end(arg); + + if (fmt_len <= empty_len) + break; /* SUCCESS */ + if (t > 0) + die("BUG: length of formatted string changed"); + + strbuf_resize(s, s->length + fmt_len); + } + + s->length += fmt_len; +} + +/* vi:ai et sw=4 ts=4: + */ diff --git a/thirdparty/lua-cjson/strbuf.h b/thirdparty/lua-cjson/strbuf.h new file mode 100644 index 000000000..5d22e67c5 --- /dev/null +++ b/thirdparty/lua-cjson/strbuf.h @@ -0,0 +1,159 @@ +/* strbuf - String buffer routines + * + * Copyright (c) 2010-2012 Mark Pulford + * + * 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. + */ + +#ifndef _STRBUF_H_ +#define _STRBUF_H_ + +#include +#include + +/* Size: Total bytes allocated to *buf + * Length: String length, excluding optional NULL terminator. + * Increment: Allocation increments when resizing the string buffer. + * Dynamic: True if created via strbuf_new() + */ + +typedef struct { + char *buf; + int size; + int length; + int increment; + int dynamic; + int reallocs; + int debug; +} strbuf_t; + +#ifndef STRBUF_DEFAULT_SIZE +#define STRBUF_DEFAULT_SIZE 1023 +#endif +#ifndef STRBUF_DEFAULT_INCREMENT +#define STRBUF_DEFAULT_INCREMENT -2 +#endif + +/* Initialise */ +extern strbuf_t *strbuf_new(int len); +extern void strbuf_init(strbuf_t *s, int len); +extern void strbuf_set_increment(strbuf_t *s, int increment); + +/* Release */ +extern void strbuf_free(strbuf_t *s); +extern char *strbuf_free_to_string(strbuf_t *s, int *len); + +/* Management */ +extern void strbuf_resize(strbuf_t *s, int len); +static int strbuf_empty_length(strbuf_t *s); +static int strbuf_length(strbuf_t *s); +static char *strbuf_string(strbuf_t *s, int *len); +static void strbuf_ensure_empty_length(strbuf_t *s, int len); +static char *strbuf_empty_ptr(strbuf_t *s); +static void strbuf_extend_length(strbuf_t *s, int len); + +/* Update */ +extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...); +extern void strbuf_append_fmt_retry(strbuf_t *s, const char *format, ...); +static void strbuf_append_mem(strbuf_t *s, const char *c, int len); +extern void strbuf_append_string(strbuf_t *s, const char *str); +static void strbuf_append_char(strbuf_t *s, const char c); +static void strbuf_ensure_null(strbuf_t *s); + +/* Reset string for before use */ +static inline void strbuf_reset(strbuf_t *s) +{ + s->length = 0; +} + +static inline int strbuf_allocated(strbuf_t *s) +{ + return s->buf != NULL; +} + +/* Return bytes remaining in the string buffer + * Ensure there is space for a NULL terminator. */ +static inline int strbuf_empty_length(strbuf_t *s) +{ + return s->size - s->length - 1; +} + +static inline void strbuf_ensure_empty_length(strbuf_t *s, int len) +{ + if (len > strbuf_empty_length(s)) + strbuf_resize(s, s->length + len); +} + +static inline char *strbuf_empty_ptr(strbuf_t *s) +{ + return s->buf + s->length; +} + +static inline void strbuf_extend_length(strbuf_t *s, int len) +{ + s->length += len; +} + +static inline int strbuf_length(strbuf_t *s) +{ + return s->length; +} + +static inline void strbuf_append_char(strbuf_t *s, const char c) +{ + strbuf_ensure_empty_length(s, 1); + s->buf[s->length++] = c; +} + +static inline void strbuf_append_char_unsafe(strbuf_t *s, const char c) +{ + s->buf[s->length++] = c; +} + +static inline void strbuf_append_mem(strbuf_t *s, const char *c, int len) +{ + strbuf_ensure_empty_length(s, len); + memcpy(s->buf + s->length, c, len); + s->length += len; +} + +static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, int len) +{ + memcpy(s->buf + s->length, c, len); + s->length += len; +} + +static inline void strbuf_ensure_null(strbuf_t *s) +{ + s->buf[s->length] = 0; +} + +static inline char *strbuf_string(strbuf_t *s, int *len) +{ + if (len) + *len = s->length; + + return s->buf; +} + +#endif + +/* vi:ai et sw=4 ts=4: + */ diff --git a/thirdparty/luabitop/lbit.c b/thirdparty/luabitop/lbit.c new file mode 100644 index 000000000..60a308155 --- /dev/null +++ b/thirdparty/luabitop/lbit.c @@ -0,0 +1,196 @@ +/* +** Lua BitOp -- a bit operations library for Lua. +** http://bitop.luajit.org/ +** +** Copyright (C) 2008-2012 Mike Pall. 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. +** +** [ MIT license: http://www.opensource.org/licenses/mit-license.php ] +*/ + +#define LUA_BITOP_VERSION "1.0.2" + +#define LUA_LIB +#include "lua.h" +#include "lauxlib.h" + +/* Singe: Lua 5.3 replaced the LUA_NUMBER_* names this library tests with LUA_FLOAT_TYPE, and 5.2 + renamed luaL_typerror. Neither changes what the library does; they only let it compile. */ +#if !defined(LUA_NUMBER_DOUBLE) && defined(LUA_FLOAT_TYPE) && defined(LUA_FLOAT_DOUBLE) && (LUA_FLOAT_TYPE == LUA_FLOAT_DOUBLE) +#define LUA_NUMBER_DOUBLE +#endif +#if LUA_VERSION_NUM >= 502 +#define luaL_typerror(L, idx, name) luaL_typeerror((L), (idx), (name)) +#endif +#include + +typedef int32_t SBits; +typedef uint32_t UBits; + +typedef union { + lua_Number n; +#ifdef LUA_NUMBER_DOUBLE + uint64_t b; +#else + UBits b; +#endif +} BitNum; + +/* Convert argument to bit type. */ +static UBits barg(lua_State *L, int idx) +{ + BitNum bn; + UBits b; + bn.n = lua_tonumber(L, idx); +#if defined(LUA_NUMBER_DOUBLE) + bn.n += 6755399441055744.0; /* 2^52+2^51 */ +#ifdef SWAPPED_DOUBLE + b = (UBits)(bn.b >> 32); +#else + b = (UBits)bn.b; +#endif +#elif defined(LUA_NUMBER_INT) || defined(LUA_NUMBER_LONG) || \ + defined(LUA_NUMBER_LONGLONG) || defined(LUA_NUMBER_LONG_LONG) || \ + defined(LUA_NUMBER_LLONG) + if (sizeof(UBits) == sizeof(lua_Number)) + b = bn.b; + else + b = (UBits)(SBits)bn.n; +#elif defined(LUA_NUMBER_FLOAT) +#error "A 'float' lua_Number type is incompatible with this library" +#else +#error "An unknown number type was supplied" +#endif + if (b == 0 && !lua_isnumber(L, idx)) { + luaL_typerror(L, idx, "number"); + } + return b; +} + +/* Return bit type. */ +#define BRET(b) lua_pushnumber(L, (lua_Number)(SBits)(b)); return 1; + +static int bit_tobit(lua_State *L) { BRET(barg(L, 1)) } +static int bit_bnot(lua_State *L) { BRET(~barg(L, 1)) } + +#define BIT_OP(FUNC_NAME, OPERATOR) \ + static int FUNC_NAME(lua_State *L) { \ + int argIndex; \ + UBits result = barg(L, 1); \ + int numArgs = lua_gettop(L); \ + \ + for (argIndex = 2; argIndex <= numArgs; ++argIndex) { \ + result OPERATOR barg(L, argIndex); \ + } \ + \ + BRET(result); \ + } + +BIT_OP(bit_band, &=) +BIT_OP(bit_bor, |=) +BIT_OP(bit_bxor, ^=) + +#define bshl(b, n) (b << n) +#define bshr(b, n) (b >> n) +#define bsar(b, n) ((SBits)b >> n) +#define brol(b, n) ((b << n) | (b >> (32-n))) +#define bror(b, n) ((b << (32-n)) | (b >> n)) + +#define BIT_SH(func, fn) \ + static int func(lua_State *L) { \ + UBits b = barg(L, 1); UBits n = barg(L, 2) & 31; BRET(fn(b, n)) } + +BIT_SH(bit_lshift, bshl) +BIT_SH(bit_rshift, bshr) +BIT_SH(bit_arshift, bsar) +BIT_SH(bit_rol, brol) +BIT_SH(bit_ror, bror) + +static int bit_bswap(lua_State *L) +{ + UBits b = barg(L, 1); + b = (b >> 24) | ((b >> 8) & 0xff00) | ((b & 0xff00) << 8) | (b << 24); + BRET(b) +} + +static int bit_tohex(lua_State *L) +{ + UBits b = barg(L, 1); + SBits n = lua_isnone(L, 2) ? 8 : (SBits)barg(L, 2); + const char *hexdigits = "0123456789abcdef"; + char buf[8]; + int i; + if (n < 0) { n = -n; hexdigits = "0123456789ABCDEF"; } + if (n > 8) n = 8; + for (i = (int)n; --i >= 0; ) { buf[i] = hexdigits[b & 15]; b >>= 4; } + lua_pushlstring(L, buf, (size_t)n); + return 1; +} + +static const struct luaL_Reg bit_funcs[] = { + { "tobit", bit_tobit }, + { "bnot", bit_bnot }, + { "band", bit_band }, + { "bor", bit_bor }, + { "bxor", bit_bxor }, + { "lshift", bit_lshift }, + { "rshift", bit_rshift }, + { "arshift", bit_arshift }, + { "rol", bit_rol }, + { "ror", bit_ror }, + { "bswap", bit_bswap }, + { "tohex", bit_tohex }, + { NULL, NULL } +}; + +/* Signed right-shifts are implementation-defined per C89/C99. +** But the de facto standard are arithmetic right-shifts on two's +** complement CPUs. This behaviour is required here, so test for it. +*/ +#define BAD_SAR (bsar(-8, 2) != (SBits)-2) + +LUALIB_API int luaopen_bit(lua_State *L) +{ + UBits b; + lua_pushnumber(L, (lua_Number)1437217655L); + b = barg(L, -1); + if (b != (UBits)1437217655L || BAD_SAR) { /* Perform a simple self-test. */ + const char *msg = "compiled with incompatible luaconf.h"; +#ifdef LUA_NUMBER_DOUBLE +#ifdef _WIN32 + if (b == (UBits)1610612736L) + msg = "use D3DCREATE_FPU_PRESERVE with DirectX"; +#endif + if (b == (UBits)1127743488L) + msg = "not compiled with SWAPPED_DOUBLE"; +#endif + if (BAD_SAR) + msg = "arithmetic right-shift broken"; + luaL_error(L, "bit library self-test failed (%s)", msg); + } + /* Singe: 5.2 dropped luaL_register; luaL_newlib builds the table and leaves no global. */ +#if LUA_VERSION_NUM < 502 + luaL_register(L, "bit", bit_funcs); +#else + luaL_newlib(L, bit_funcs); +#endif + return 1; +} diff --git a/thirdparty/md5/md5.c b/thirdparty/md5/md5.c new file mode 100644 index 000000000..77c8736e6 --- /dev/null +++ b/thirdparty/md5/md5.c @@ -0,0 +1,269 @@ +/** +* $Id: md5.c,v 1.2 2008/03/24 20:59:12 mascarenhas Exp $ +* Hash function MD5 +* @author Marcela Ozorio Suarez, Roberto I. +*/ + + +#include +#include +#include +#include + +#include "md5.h" + + +#define WORD 32 +#define MASK 0xFFFFFFFF + +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + + +/** +* md5 hash function. +* @param message: aribtary string. +* @param len: message length. +* @param output: buffer to receive the hash value. Its size must be +* (at least) HASHSIZE. +*/ +void md5(const unsigned char *message, size_t len, unsigned char output[HASHSIZE]); + +/** +* init a new md5 calculate context +* @param m a uninitialized md5_t type context +*/ +void md5_init(md5_t *m); + +/** +* update message to md5 context +* @param m a initialized md5_t type context +* @param message aribtary string +* @param len message length +* @return true if update completed, means len is not the multiples of 64. +* false if you can continue update. +*/ +void md5_update(md5_t *m, const unsigned char *message, size_t len); + +/** + * finish md5 calculate. + * @param m a md5_type context which previous md5_update on it return true. + * @param output buffer to receive the hash value. its size must be + * (at least) HASHSIZE. + */ +void md5_finish(md5_t *m, unsigned char output[HASHSIZE]); + +/* +** Realiza a rotacao no sentido horario dos bits da variavel 'D' do tipo WORD32. +** Os bits sao deslocados de 'num' posicoes +*/ +#define ROTATE_LEFT(D, num) (D<>(WORD-num)) + +/*Macros que definem operacoes relizadas pelo algoritmo md5 */ +#define F(x, y, z) (((x) & (y)) | ((~(x)) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~(z)))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~(z)))) + +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (WORD32)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (WORD32)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (WORD32)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (WORD32)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +static const unsigned char PAD[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static void encode(unsigned char* output, const WORD32* input, unsigned int len) { + unsigned int i, j; + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j + 1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j + 2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j + 3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +static void decode(WORD32* output, const unsigned char* input, unsigned int len) { + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((WORD32)input[j]) | (((WORD32)input[j + 1]) << 8) | + (((WORD32)input[j + 2]) << 16) | (((WORD32)input[j + 3]) << 24); +} + +static void md5_transform(WORD32* digest, const unsigned char* block) { + WORD32 a = digest[0], b = digest[1], c = digest[2], d = digest[3], x[16]; + + decode(x, block, 64); + + /* Round 1 */ + FF(a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */ + FF(d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */ + FF(c, d, a, b, x[2], S13, 0x242070db); /* 3 */ + FF(b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */ + FF(a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */ + FF(d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */ + FF(c, d, a, b, x[6], S13, 0xa8304613); /* 7 */ + FF(b, c, d, a, x[7], S14, 0xfd469501); /* 8 */ + FF(a, b, c, d, x[8], S11, 0x698098d8); /* 9 */ + FF(d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */ + FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG(a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */ + GG(d, a, b, c, x[6], S22, 0xc040b340); /* 18 */ + GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG(b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */ + GG(a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */ + GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG(b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */ + GG(a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */ + GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG(c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */ + + + + GG(b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */ + GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG(d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */ + GG(c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */ + GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH(a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */ + HH(d, a, b, c, x[8], S32, 0x8771f681); /* 34 */ + HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH(a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */ + HH(d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */ + HH(c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */ + HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH(d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */ + HH(c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */ + HH(b, c, d, a, x[6], S34, 0x4881d05); /* 44 */ + HH(a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */ + HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH(b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II(a, b, c, d, x[0], S41, 0xf4292244); /* 49 */ + II(d, a, b, c, x[7], S42, 0x432aff97); /* 50 */ + II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II(b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */ + II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II(d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */ + II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II(b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */ + II(a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */ + II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II(c, d, a, b, x[6], S43, 0xa3014314); /* 59 */ + II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II(a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */ + II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II(c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */ + II(b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */ + + digest[0] += a; + digest[1] += b; + digest[2] += c; + digest[3] += d; +} + +void md5(const unsigned char *message, size_t len, unsigned char output[HASHSIZE]) { + md5_t m; + md5_init(&m); + md5_update(&m, message, len); + md5_finish(&m, output); +} + +void md5_init(md5_t *m) { + m->d[0] = 0x67452301; + m->d[1] = 0xEFCDAB89; + m->d[2] = 0x98BADCFE; + m->d[3] = 0x10325476; + m->c[0] = 0; + m->c[1] = 0; +} + +void md5_update(md5_t *m, const unsigned char *message, size_t len) { + if (len <= 0) + return; + + unsigned int i, index, bits, partLen; + index = (unsigned int)((m->c[0] >> 3) & 0x3f); + bits = (unsigned int)(len << 3); + if ((m->c[0] += bits) < bits) + m->c[1]++; + m->c[1] += (unsigned int)(len >> 29); + + partLen = 64 - index; + if (len >= partLen) { + memcpy(m->in + index, message, partLen); + md5_transform(m->d, m->in); + + for (i = partLen; i + 63 < len; i += 64) { + md5_transform(m->d, message + i); + } + index = 0; + } + else { + i = 0; + } + memcpy(m->in + index, message + i, len - i); +} + +void md5_finish(md5_t *m, unsigned char output[HASHSIZE]) { + unsigned char bits[8]; + unsigned int index, padLen; + encode(bits, m->c, 8); + + index = (unsigned int)((m->c[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + md5_update(m, PAD, padLen); + + md5_update(m, bits, 8); + + encode(output, m->d, 16); +} diff --git a/thirdparty/md5/md5.h b/thirdparty/md5/md5.h new file mode 100644 index 000000000..78d5f52d6 --- /dev/null +++ b/thirdparty/md5/md5.h @@ -0,0 +1,41 @@ +/** +* $Id: md5.h,v 1.2 2006/03/03 15:04:49 tomas Exp $ +* Cryptographic module for Lua. +* @author Roberto Ierusalimschy +*/ + + +#ifndef md5_h +#define md5_h + +#include "lua.h" +#include + + +#define HASHSIZE 16 + +#if __STDC_VERSION__ >= 199901L +#include +typedef uint32_t WORD32; +#else +/* static assert that int equal or greater than 32bit. */ +typedef char static_assert_sizeof_int +[sizeof(unsigned int) >= 4 ? 1 : -1]; +typedef unsigned int WORD32; +#endif + +typedef struct md5_t { + WORD32 d[4]; + WORD32 c[2]; + unsigned char in[64]; +} md5_t; + +void md5_init(md5_t *m); +void md5_update(md5_t *m, const unsigned char *message, size_t len); +void md5_finish(md5_t *m, unsigned char output[HASHSIZE]); +void md5(const unsigned char *message, size_t len, unsigned char output[HASHSIZE]); + +LUALIB_API int luaopen_md5_core (lua_State *L); + + +#endif diff --git a/thirdparty/md5/md5lib.c b/thirdparty/md5/md5lib.c new file mode 100644 index 000000000..c760a4f80 --- /dev/null +++ b/thirdparty/md5/md5lib.c @@ -0,0 +1,241 @@ +/** +* $Id: md5lib.c,v 1.10 2008/05/12 20:51:27 carregal Exp $ +* Cryptographic and Hash functions for Lua +* @author Roberto Ierusalimschy +*/ + + +#include +#include +#include + +#include "lua.h" +#include "lauxlib.h" + +#include "md5.h" + +#define MAXKEY 256 +#define BLOCKSIZE 16 + +/** +* Hash function. Returns a hash for a given string. +* @param message: arbitrary binary string. +* @return A 128-bit hash string. +*/ +static int lmd5 (lua_State *L) { + char buff[16]; + size_t l; + const char *message = luaL_checklstring(L, 1, &l); + md5((const unsigned char *)message, l, (unsigned char *)buff); + lua_pushlstring(L, buff, 16L); + return 1; +} + +static int xmd5 (lua_State *L) { + static char tab[] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + int i; + char buff[16]; + char hexbuff[32]; + size_t l; + const char *message = luaL_checklstring(L, 1, &l); + md5((const unsigned char *)message, l, (unsigned char *)buff); + + for (i = 0; i < 16; i++) + { + hexbuff[i*2] = tab[(buff[i] >> 4) & 15]; + hexbuff[i*2+1] = tab[buff[i] & 15]; + } + + lua_pushlstring(L, hexbuff, 32L); + return 1; +} + +/** +* X-Or. Does a bit-a-bit exclusive-or of two strings. +* @param s1: arbitrary binary string. +* @param s2: arbitrary binary string with same length as s1. +* @return a binary string with same length as s1 and s2, +* where each bit is the exclusive-or of the corresponding bits in s1-s2. +*/ +static int ex_or (lua_State *L) { + size_t l1, l2; + const char *s1 = luaL_checklstring(L, 1, &l1); + const char *s2 = luaL_checklstring(L, 2, &l2); + luaL_Buffer b; + luaL_argcheck( L, l1 == l2, 2, "lengths must be equal" ); + luaL_buffinit(L, &b); + while (l1--) luaL_addchar(&b, (*s1++)^(*s2++)); + luaL_pushresult(&b); + return 1; +} + + +static void checkseed (lua_State *L) { + if (lua_isnone(L, 3)) { /* no seed? */ + time_t tm = time(NULL); /* for `random' seed */ + lua_pushlstring(L, (char *)&tm, sizeof(tm)); + } +} + + +// Singe: Lua 5.2 and later provide luaL_setfuncs, so this stand-in is only for 5.1. +#if LUA_VERSION_NUM < 502 +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 */ +} +#endif + +static int initblock (lua_State *L, const char *seed, int lseed, char *block) { + size_t lkey; + const char *key = luaL_checklstring(L, 2, &lkey); + if (lkey > MAXKEY) + luaL_error(L, "key too long (> %d)", MAXKEY); + memset(block, 0, BLOCKSIZE); + memcpy(block, seed, lseed); + memcpy(block+BLOCKSIZE, key, lkey); + return (int)lkey+BLOCKSIZE; +} + + +static void codestream (lua_State *L, const char *msg, size_t lmsg, + char *block, int lblock) { + luaL_Buffer b; + luaL_buffinit(L, &b); + while (lmsg > 0) { + char code[BLOCKSIZE]; + int i; + md5((const unsigned char *)block, lblock, (unsigned char *)code); + for (i=0; i 0; i++, lmsg--) + code[i] ^= *msg++; + luaL_addlstring(&b, code, i); + memcpy(block, code, i); /* update seed */ + } + luaL_pushresult(&b); +} + + +static void decodestream (lua_State *L, const char *cypher, size_t lcypher, + char *block, int lblock) { + luaL_Buffer b; + luaL_buffinit(L, &b); + while (lcypher > 0) { + char code[BLOCKSIZE]; + int i; + md5((const unsigned char *)block, lblock, (unsigned char *)code); /* update seed */ + for (i=0; i 0; i++, lcypher--) + code[i] ^= *cypher++; + luaL_addlstring(&b, code, i); + memcpy(block, cypher-i, i); + } + luaL_pushresult(&b); +} + + +/** +* Encrypts a string. Uses the hash function md5 in CFB (Cipher-feedback +* mode). +* @param message: arbitrary binary string to be encrypted. +* @param key: arbitrary binary string to be used as a key. +* @param [seed]: optional arbitrary binary string to be used as a seed. +* if no seed is provided, the function uses the result of +* time() as a seed. +* @return The cyphertext (as a binary string). +*/ +static int crypt (lua_State *L) { + size_t lmsg; + const char *msg = luaL_checklstring(L, 1, &lmsg); + size_t lseed; + const char *seed; + int lblock; + char block[BLOCKSIZE+MAXKEY]; + checkseed(L); + seed = luaL_checklstring(L, 3, &lseed); + if (lseed > BLOCKSIZE) + luaL_error(L, "seed too long (> %d)", BLOCKSIZE); + /* put seed and seed length at the beginning of result */ + block[0] = (char)lseed; + memcpy(block+1, seed, lseed); + lua_pushlstring(L, block, lseed+1); /* to concat with result */ + lblock = initblock(L, seed, lseed, block); + codestream(L, msg, lmsg, block, lblock); + lua_concat(L, 2); + return 1; +} + + +/** +* Decrypts a string. For any message, key, and seed, we have that +* decrypt(crypt(msg, key, seed), key) == msg. +* @param cyphertext: message to be decrypted (this must be the result of + a previous call to crypt. +* @param key: arbitrary binary string to be used as a key. +* @return The plaintext. +*/ +static int decrypt (lua_State *L) { + size_t lcyphertext; + const char *cyphertext = luaL_checklstring(L, 1, &lcyphertext); + size_t lseed = cyphertext[0]; + const char *seed = cyphertext+1; + int lblock; + char block[BLOCKSIZE+MAXKEY]; + if (!(lcyphertext >= lseed + 1 && lseed <= BLOCKSIZE)) { + lua_pushlstring(L, NULL, 0); + lua_pushboolean(L, 0); + return 2; + } + cyphertext += lseed+1; + lcyphertext -= lseed+1; + lblock = initblock(L, seed, lseed, block); + decodestream(L, cyphertext, lcyphertext, block, lblock); + lua_pushboolean(L, 1); + return 2; +} + + +/* +** Assumes the table is on top of the stack. +*/ +static void set_info (lua_State *L) { + lua_pushliteral (L, "_COPYRIGHT"); + lua_pushliteral (L, "Copyright (C) 2003-2019 PUC-Rio"); + lua_settable (L, -3); + lua_pushliteral (L, "_DESCRIPTION"); + lua_pushliteral (L, "Checksum facilities for Lua"); + lua_settable (L, -3); + lua_pushliteral (L, "_VERSION"); + lua_pushliteral (L, "MD5 1.3"); + lua_settable (L, -3); +} + +static struct luaL_Reg md5lib[] = { + {"sum", lmd5}, + {"hexsum", xmd5}, + {"exor", ex_or}, + {"crypt", crypt}, + {"decrypt", decrypt}, + {NULL, NULL} +}; + +int luaopen_md5_core (lua_State *L) { +#if LUA_VERSION_NUM < 502 + lua_newtable(L); + luaL_register(L, "md5", md5lib); + luaL_setfuncs(L, md5lib, 0); +#else + /* Singe: 5.2 dropped luaL_register. luaL_newlib makes the table and fills it, and the module + system binds the name, so nothing is written to the globals here. */ + luaL_newlib(L, md5lib); +#endif + set_info (L); + return 1; +} diff --git a/util/packGlb.py b/util/packGlb.py index 2f9b8a317..a82103fca 100644 --- a/util/packGlb.py +++ b/util/packGlb.py @@ -18,14 +18,19 @@ for b in g.get('buffers', []): for v in views: v['byteOffset'] = v.get('byteOffset', 0) + offsets[v.get('buffer', 0)] v['buffer'] = 0 -# Images: each becomes a buffer view with a mime type. +# Images: each becomes a buffer view with a mime type. The table is exhaustive on purpose: a +# two-way png/jpeg guess quietly labelled every .ktx2 as image/jpeg, which Singe survives because it +# sniffs the magic number, and which no other glTF reader has to. +mimeTypes = {'png': 'image/png', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'ktx2': 'image/ktx2', 'webp': 'image/webp', 'basis': 'image/basis'} for img in g.get('images', []): uri = img.pop('uri') data = open(os.path.join(base, uri), 'rb').read() ext = uri.lower().rsplit('.', 1)[-1] + if ext not in mimeTypes: + sys.exit('No mime type known for ' + uri + '; add it to mimeTypes in util/packGlb.py.') views.append({'buffer': 0, 'byteOffset': add(data), 'byteLength': len(data)}) img['bufferView'] = len(views) - 1 - img['mimeType'] = 'image/png' if ext == 'png' else 'image/jpeg' + img['mimeType'] = mimeTypes[ext] while len(binary) % 4: binary.append(0) g['buffers'] = [{'byteLength': len(binary)}] js = json.dumps(g, separators=(',', ':')).encode()