diff --git a/.github/scripts/create-app.sh b/.github/scripts/create-app.sh index 2f539a485..04f3b0315 100755 --- a/.github/scripts/create-app.sh +++ b/.github/scripts/create-app.sh @@ -48,6 +48,15 @@ PlistBuddy ${APPROOT}/Contents/Info.plist -c "add CFBundleIconFile string AppIco PlistBuddy ${APPROOT}/Contents/Info.plist -c "add NSHighResolutionCapable bool true" PlistBuddy ${APPROOT}/Contents/version.plist -c "add ProjectName string ${APP}" +PlistBuddy ${APPROOT}/Contents/Info.plist -c "add CFBundleExecutable string ${APP}" +PlistBuddy ${APPROOT}/Contents/Info.plist -c "add CFBundleDevelopmentRegion string en" +PlistBuddy ${APPROOT}/Contents/Info.plist -c "add CFBundleInfoDictionaryVersion string 6.0" +PlistBuddy ${APPROOT}/Contents/Info.plist -c "add CFBundleName string ${APP}" +PlistBuddy ${APPROOT}/Contents/Info.plist -c "add CFBundlePackageType string APPL" +PlistBuddy ${APPROOT}/Contents/Info.plist -c "add NSHumanReadableCopyright string Copyright PCSX-Redux Authors" + +PlistBuddy ${APPROOT}/Contents/Info.plist -c "add LSMinimumSystemVersion string 10.15" + # Install dylib dependencies in ./Contents/Frameworks. # Update the dyld load commands for these. dylibbundler -od -b -x ${APPROOT}/Contents/MacOS/${APP} -d ${APPROOT}/Contents/Frameworks/ -p @rpath diff --git a/.github/workflows/linux-asan.yml b/.github/workflows/linux-asan.yml new file mode 100644 index 000000000..5c791727d --- /dev/null +++ b/.github/workflows/linux-asan.yml @@ -0,0 +1,34 @@ +name: Linux CI asan + +on: + push: + branches: + - main + pull_request: + +jobs: + asan: + runs-on: ubuntu-latest + container: + image: ghcr.io/grumpycoders/pcsx-redux-build:latest + env: + TEST_RESULTS: /tmp/test-results + BUILD: asan + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + set-safe-directory: true + - uses: n1hility/cancel-previous-runs@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + - run: | + make -j 2 all pcsx-redux-tests + make -C src/mips/openbios -j 2 BUILD=Release + cp src/mips/openbios/openbios.bin . + make -C src/mips/openbios clean + make -C src/mips/tests -j 2 PCSX_TESTS=true BUILD=Release + cp ./openbios.bin src/mips/openbios/ + - name: Test + run: | + xvfb-run ./pcsx-redux-tests diff --git a/.gitignore b/.gitignore index a8caed080..3c5a53ced 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,9 @@ fb.bat *.idb # Runtime files in ./resources +16-to-24.frag +16-to-24.lua +16-to-24.vert offscreen.frag offscreen.lua offscreen.vert @@ -110,3 +113,6 @@ comport.txt # Various editors' backup files *.bak + +# NVIDIA Nsight +GPUCache diff --git a/.gitmodules b/.gitmodules index 692f8f789..bb12a81fe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -33,7 +33,7 @@ url = https://github.com/luvit/luv.git [submodule "third_party/freetype"] path = third_party/freetype - url = https://gitlab.freedesktop.org/freetype/freetype.git + url = https://github.com/freetype/freetype.git [submodule "third_party/stb"] path = third_party/stb url = https://github.com/nothings/stb.git @@ -85,3 +85,6 @@ [submodule "third_party/nanosvg"] path = third_party/nanosvg url = https://github.com/grumpycoders/nanosvg +[submodule "third_party/luafilesystem"] + path = third_party/luafilesystem + url = https://github.com/lunarmodules/luafilesystem.git diff --git a/AppImageBuilder.yml b/AppImageBuilder.yml index 3bfa03ff9..0e64c40d7 100644 --- a/AppImageBuilder.yml +++ b/AppImageBuilder.yml @@ -18,17 +18,17 @@ AppDir: - amd64 allow_unauthenticated: true sources: - - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ kinetic main restricted - - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ kinetic-updates main restricted - - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ kinetic universe - - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ kinetic-updates universe - - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ kinetic multiverse - - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ kinetic-updates multiverse - - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ kinetic-backports main + - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ lunar main restricted + - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ lunar-updates main restricted + - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ lunar universe + - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ lunar-updates universe + - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ lunar multiverse + - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ lunar-updates multiverse + - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ lunar-backports main restricted universe multiverse - - sourceline: deb http://security.ubuntu.com/ubuntu kinetic-security main restricted - - sourceline: deb http://security.ubuntu.com/ubuntu kinetic-security universe - - sourceline: deb http://security.ubuntu.com/ubuntu kinetic-security multiverse + - sourceline: deb http://security.ubuntu.com/ubuntu lunar-security main restricted + - sourceline: deb http://security.ubuntu.com/ubuntu lunar-security universe + - sourceline: deb http://security.ubuntu.com/ubuntu lunar-security multiverse include: - libbz2-1.0:amd64 - libcom-err2:amd64 diff --git a/LICENSES.md b/LICENSES.md index 5ced6b594..2d092131d 100644 --- a/LICENSES.md +++ b/LICENSES.md @@ -29,7 +29,6 @@ find a link to the vendored version itself. - [FreeType](https://gitlab.freedesktop.org/freetype/freetype.git) - [gl3w](https://github.com/skaslev/gl3w) ([vendored](https://github.com/grumpycoders/pcsx-redux/tree/main/third_party/gl3w/GL)) - [glfw](https://www.glfw.org) - - [luajit-opencl](https://github.com/malkia/luajit-opencl) ([vendored](https://github.com/grumpycoders/pcsx-redux/tree/main/third_party/glffi)) - [googletest](https://github.com/google/googletest) - [GoogleTestAdapter](https://github.com/csoltenborn/GoogleTestAdapter) ([vendored](https://github.com/grumpycoders/pcsx-redux/tree/main/third_party/GoogleTestAdapter)) - [http-parser](https://github.com/nodejs/http-parser) @@ -41,8 +40,11 @@ find a link to the vendored version itself. - [json](https://github.com/nlohmann/json) ([vendored](https://github.com/grumpycoders/pcsx-redux/blob/main/third_party/json.hpp)) - [libcester](https://github.com/exoticlibraries/libcester) - [libuv](https://github.com/libuv/libuv) + - [LPeg](http://www.inf.puc-rio.br/~roberto/lpeg/) - [lua-protobuf](https://github.com/starwing/lua-protobuf) ([vendored](https://github.com/grumpycoders/pcsx-redux/tree/main/third_party/lua-protobuf)) + - [luafilesystem](https://github.com/lunarmodules/luafilesystem) - [LuaJIT](https://github.com/LuaJIT/LuaJIT) ([vendored](https://github.com/grumpycoders/LuaJIT/tree/vendored-clib-virtual)) + - [luajit-opencl](https://github.com/malkia/luajit-opencl) ([vendored](https://github.com/grumpycoders/pcsx-redux/tree/main/third_party/glffi)) - [luv](https://github.com/luvit/luv) - [magic_enum](https://github.com/Neargye/magic_enum) - [md4c](https://github.com/mity/md4c) diff --git a/Makefile b/Makefile index e529f1e43..dd1472a03 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ else HAS_SUBMODULES = true endif -CXXFLAGS += -std=c++2a +CXXFLAGS += -std=c++2b CPPFLAGS += `pkg-config --cflags $(PACKAGES)` CPPFLAGS += -I. CPPFLAGS += -Isrc @@ -58,6 +58,7 @@ else endif CPPFLAGS_asan += -O1 -fsanitize=address -fno-omit-frame-pointer CPPFLAGS_ubsan += -O1 -fsanitize=undefined -fno-omit-frame-pointer +CPPFLAGS_lto += -O3 -flto=auto -fno-fat-lto-objects -flto-partition=one CPPFLAGS_ReleaseWithTracy += -O3 -DTRACY_ENABLE ifeq ($(CC_IS_CLANG),true) @@ -93,6 +94,7 @@ else endif LDFLAGS_asan += -fsanitize=address LDFLAGS_ubsan += -fsanitize=undefined +LDFLAGS_lto += -O3 -flto=auto -flto-partition=one CPPFLAGS += $(CPPFLAGS_$(BUILD)) -pthread LDFLAGS += $(LDFLAGS_$(BUILD)) -pthread @@ -126,7 +128,9 @@ SRCS += third_party/imgui/misc/freetype/imgui_freetype.cpp SRCS += third_party/imgui_lua_bindings/imgui_lua_bindings.cpp SRCS += third_party/imgui_md/imgui_md.cpp SRCS += third_party/imgui_memory_editor/imgui_memory_editor.cpp +SRCS += $(wildcard third_party/lpeg/*.c) SRCS += third_party/lua-protobuf/pb.c +SRCS += third_party/luafilesystem/src/lfs.c SRCS += third_party/luv/src/luv.c SRCS += third_party/md4c/src/md4c.c SRCS += third_party/multipart-parser-c/multipart_parser.c diff --git a/README.md b/README.md index 3615ab795..03879b782 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,13 @@ |Linux build|[![Linux CI](https://github.com/grumpycoders/pcsx-redux/workflows/Linux%20CI/badge.svg?branch=main)](https://github.com/grumpycoders/pcsx-redux/actions?query=workflow%3A%22Linux+CI%22+branch%3Amain)|[Linux Intel 64-bits (AppImage)](https://install.appcenter.ms/orgs/grumpycoders/apps/pcsx-redux-linux64/distribution_groups/public)| |MacOS build|[![MacOS CI](https://github.com/grumpycoders/pcsx-redux/workflows/macOS%20CI/badge.svg?branch=main)](https://github.com/grumpycoders/pcsx-redux/actions?query=workflow%3A%22macOS+CI%22+branch%3Amain)|[MacOS](https://install.appcenter.ms/orgs/grumpycoders/apps/pcsx-redux-macos/distribution_groups/public)| -To discuss this emulator specifically, please join our Discord server: [![Discord](https://img.shields.io/discord/567975889879695361)](https://discord.gg/KG5uCqw) +To discuss this emulator specifically, please join our Discord server: -To discuss PlayStation 1 development, hacking, and reverse engineering in general, please join the PSX.Dev Discord server: [![Discord](https://img.shields.io/discord/642647820683444236)](https://discord.gg/QByKPpH) +[![Discord](https://discord.com/api/guilds/567975889879695361/widget.png?style=banner2)](https://discord.gg/KG5uCqw) + +To discuss PlayStation 1 development, hacking, and reverse engineering in general, please join the PSX.Dev Discord server: + +[![Discord](https://discord.com/api/guilds/642647820683444236/widget.png?style=banner2)](https://discord.gg/QByKPpH) # PCSX-Redux @@ -72,7 +76,7 @@ powershell -c "& { iwr -UseBasicParsing https://bit.ly/mips-ps1 | iex }" Then, open a new command prompt, and type the following: ``` -mips install 13.1.0 +mips install 13.2.0 ``` To manually install this script, you can download it from [here](https://bit.ly/mips-ps1), and then install it with the following command: @@ -83,8 +87,10 @@ powershell -ExecutionPolicy Unrestricted -File mips.ps1 self-install C:\path\to\ You can leave the installation path blank to install the script in the Application Data folder. +Once the toolchain is installed, you can compile OpenBIOS using `make -C src/mips/openbios`. + ### Linux -Run `./dockermake.sh`. You need [docker](https://en.wikipedia.org/wiki/Docker_(software)) for this to work. You will also need a few libraries on your system for this to work. Check the [Dockerfile](https://github.com/grumpycoders/pcsx-redux/blob/main/tools/build/Dockerfile#L22) for a list of library packages to install. Alternatively, if you do not want to use Docker, you can also simply install the dependencies listed below and run `make`. +Run `./dockermake.sh appimage`. You need [docker](https://en.wikipedia.org/wiki/Docker_(software)) for this to work. This will create an [AppImage](https://appimage.org/) file, called `PCSX-Redux-HEAD-x86_64.AppImage`, which a self-hosted binary containing all of its required dependencies. Alternatively, if you do not want to use Docker, you can also simply install the dependencies listed below and run `make`. #### GNU/Linux Dependencies diff --git a/src/cdrom/cdriso-cue.cc b/src/cdrom/cdriso-cue.cc index 543f66850..ec7fc4ff6 100644 --- a/src/cdrom/cdriso-cue.cc +++ b/src/cdrom/cdriso-cue.cc @@ -80,30 +80,31 @@ bool PCSX::CDRIso::parsecue(const char *isofileString) { } file->opaque = fi; file->destroy = [](CueFile *file) { - UvFile *fi = reinterpret_cast(file->opaque); + File *fi = reinterpret_cast(file->opaque); + fi->close(); delete fi; file->opaque = nullptr; }; file->close = [](CueFile *file, CueScheduler *scheduler, void (*cb)(CueFile *, CueScheduler *)) { - UvFile *fi = reinterpret_cast(file->opaque); + File *fi = reinterpret_cast(file->opaque); fi->close(); File_schedule_close(file, scheduler, cb); }; file->size = [](CueFile *file, CueScheduler *scheduler, int compressed, void (*cb)(CueFile *, CueScheduler *, uint64_t)) { - UvFile *fi = reinterpret_cast(file->opaque); - if (compressed) { - FFmpegAudioFile *cfi = new FFmpegAudioFile(fi, FFmpegAudioFile::CHANNELS_STEREO, - FFmpegAudioFile::ENDIANNESS_LITTLE, 44100); + File *fi = reinterpret_cast(file->opaque); + if (compressed && dynamic_cast(fi)) { + FFmpegAudioFile *cfi = + new FFmpegAudioFile(fi, FFmpegAudioFile::Channels::Stereo, FFmpegAudioFile::Endianness::Little, + FFmpegAudioFile::SampleFormat::S16, 44100); file->opaque = cfi; - File_schedule_size(file, scheduler, cfi->size(), cb); - } else { - File_schedule_size(file, scheduler, fi->size(), cb); + fi = cfi; } + File_schedule_size(file, scheduler, fi->size(), cb); }; file->read = [](CueFile *file, CueScheduler *scheduler, uint32_t amount, uint64_t cursor, uint8_t *buffer, void (*cb)(CueFile *, CueScheduler *, int error, uint32_t amount, uint8_t *buffer)) { - UvFile *fi = reinterpret_cast(file->opaque); + File *fi = reinterpret_cast(file->opaque); if (cursor >= fi->size()) { File_schedule_read(file, scheduler, 0, 0, nullptr, cb); } else { @@ -135,12 +136,20 @@ bool PCSX::CDRIso::parsecue(const char *isofileString) { Context *context = reinterpret_cast(scheduler->opaque); if (error) { context->failed = true; - g_system->log(LogClass::CDROM_IO, "Error parsing Cue File: %s", error); + PCSX::g_system->printf("Error parsing Cue File: %s", error); } }); Scheduler_run(&scheduler); - CueParser_destroy(&parser); + CueParser_close(&parser, &scheduler, [](CueParser *parser, CueScheduler *scheduler, const char *error) { + Context *context = reinterpret_cast(scheduler->opaque); + if (error) { + context->failed = true; + PCSX::g_system->printf("Error closing cue parser: %s", error); + } + CueParser_destroy(parser); + }); + Scheduler_run(&scheduler); File_schedule_close(&cue, &scheduler, [](CueFile *file, CueScheduler *scheduler) { file->destroy(file); }); if (context.failed) { @@ -148,8 +157,10 @@ bool PCSX::CDRIso::parsecue(const char *isofileString) { CueTrack *track = &disc.tracks[i]; if (track->file) { if (track->file->references == 1) { - File_schedule_close(track->file, &scheduler, - [](CueFile *file, CueScheduler *scheduler) { file->destroy(file); }); + File_schedule_close(track->file, &scheduler, [](CueFile *file, CueScheduler *scheduler) { + file->destroy(file); + free(file); + }); } else { track->file->references--; } @@ -172,6 +183,12 @@ bool PCSX::CDRIso::parsecue(const char *isofileString) { m_ti[i].start = IEC60908b::MSF(track->indices[1] + 150); m_ti[i].pregap = IEC60908b::MSF(track->indices[1] - track->indices[0]); m_ti[i].length = IEC60908b::MSF(track->size); + if (track->file->references == 1) { + free(track->file); + } else { + track->file->references--; + } + track->file = nullptr; } m_numtracks = disc.trackCount; diff --git a/src/cdrom/cdriso-sbi.cc b/src/cdrom/cdriso-sbi.cc index 1e834da97..f6deb047b 100644 --- a/src/cdrom/cdriso-sbi.cc +++ b/src/cdrom/cdriso-sbi.cc @@ -20,14 +20,13 @@ #include "cdrom/cdriso.h" bool PCSX::CDRIso::LoadSBI(const char *filename) { - IO sbihandle; + IO sbihandle(new UvFile(filename)); char buffer[16]; - sbihandle.setFile(new UvFile(filename)); + if (sbihandle->failed()) return false; if (g_emulator->settings.get()) { sbihandle.asA()->startCaching(); } - if (sbihandle->failed()) return false; // init sbicount = 0; diff --git a/src/cdrom/cdriso.cc b/src/cdrom/cdriso.cc index c22be656e..990d12d2c 100644 --- a/src/cdrom/cdriso.cc +++ b/src/cdrom/cdriso.cc @@ -257,20 +257,15 @@ void PCSX::CDRIso::printTracks() { } } -// This function is invoked by the front-end when opening an ISO -// file for playback -bool PCSX::CDRIso::open(void) { - // is it already open? - if (m_cdHandle) return true; - - m_cdHandle.setFile(new UvFile(m_isoPath)); - if (g_emulator->settings.get()) { - m_cdHandle.asA()->startCaching(); - } +bool PCSX::CDRIso::open(IO isoFile) { + m_cdHandle = isoFile; if (m_cdHandle->failed()) { m_cdHandle.reset(); return false; } + if (g_emulator->settings.get() && m_cdHandle.isA()) { + m_cdHandle.asA()->startCaching(); + } PCSX::g_system->printf(_("Loaded CD Image: %s"), m_isoPath.string()); diff --git a/src/cdrom/cdriso.h b/src/cdrom/cdriso.h index 1af4b14d8..117908150 100644 --- a/src/cdrom/cdriso.h +++ b/src/cdrom/cdriso.h @@ -33,10 +33,13 @@ namespace PCSX { class CDRIso { public: - CDRIso(); CDRIso(const std::filesystem::path& path) : CDRIso() { m_isoPath = path; - open(); + open(new UvFile(m_isoPath)); + } + CDRIso(IO isoFile) : CDRIso() { + m_isoPath = isoFile->filename(); + open(isoFile); } ~CDRIso() { close(); @@ -68,7 +71,8 @@ class CDRIso { bool CheckSBI(const uint8_t* time); private: - bool open(); + CDRIso(); + bool open(IO isoFile); void close(); std::filesystem::path m_isoPath; @@ -139,7 +143,7 @@ class CDRIso { IEC60908b::MSF start; IEC60908b::MSF length; IO handle = nullptr; // for multi-track images CDDA - enum cddatype_t { NONE = 0, BIN = 1, CCDDA = 2 } cddatype = NONE; // BIN, WAV, MP3, APE + enum CDDAType { NONE = 0, BIN = 1, CCDDA = 2 } cddatype = NONE; // BIN, WAV, MP3, APE uint32_t start_offset = 0; // byte offset from start of above file }; diff --git a/src/cdrom/file.cc b/src/cdrom/file.cc index 96668e4c0..61df4140c 100644 --- a/src/cdrom/file.cc +++ b/src/cdrom/file.cc @@ -157,11 +157,12 @@ ssize_t PCSX::CDRIsoFile::read(void* buffer_, size_t size) { while (toCopy != 0) { if (m_cachedLBA != lba) { m_cachedLBA = lba; - auto res = m_iso->readSectors(lba++, m_cachedSector, 1); + auto res = m_iso->readSectors(lba, m_cachedSector, 1); if (res != 1) return -1; } size_t blocSize = std::min(toCopy, c_sectorSizes[modeIndex] - sectorOffset); memcpy(buffer + actualSize, m_cachedSector + c_sectorOffsets[modeIndex] + sectorOffset, blocSize); + lba++; sectorOffset = 0; actualSize += blocSize; toCopy -= blocSize; diff --git a/src/core/OpenGL_GPU/gpu_opengl.cc b/src/core/OpenGL_GPU/gpu_opengl.cc index 9dc0fecf3..250b5efb5 100644 --- a/src/core/OpenGL_GPU/gpu_opengl.cc +++ b/src/core/OpenGL_GPU/gpu_opengl.cc @@ -77,7 +77,7 @@ void PCSX::OpenGL_GPU::clearVRAM() { clearVRAM(0.f, 0.f, 0.f, 1.f); } // Do not forget to call this with an active OpenGL context. int PCSX::OpenGL_GPU::initBackend(UI *ui) { - m_gui = dynamic_cast(ui); + m_gui = dynamic_cast(ui); // Reserve some size for vertices & vram transfers to avoid dynamic allocations later. m_vertices.resize(vertexBufferSize); @@ -308,6 +308,105 @@ int PCSX::OpenGL_GPU::initBackend(UI *ui) { const auto vramSamplerLoc = OpenGL::uniformLocation(m_program, "u_vramTex"); glUniform1i(vramSamplerLoc, 0); // Make the fragment shader read from currently binded texture + m_vramTexture24.create(1024, 512, GL_RGBA8); + m_fbo24.createWithDrawTexture(m_vramTexture24); + m_shaderEditor24.init(); + m_shaderEditor24.reset(m_gui); + m_shaderEditor24.setDefaults(); + m_shaderEditor24.setText(R"(#version 330 core + +precision highp float; +layout (location = 0) in vec2 Position; +layout (location = 1) in vec2 UV; +layout (location = 2) in vec4 Color; +out vec2 Frag_UV; +out vec4 Frag_Color; +void main() { + Frag_UV = UV; + Frag_Color = Color; + gl_Position = vec4(Position.xy, 0, 1); +} +)", + R"(#version 330 core + +precision highp float; +uniform sampler2D u_vramTexture; +in vec2 Frag_UV; +in vec4 Frag_Color; +layout (location = 0) out vec4 Out_Color; + +uniform int u_24shift = 0; + +int texelToRaw(in vec4 t) { + int c = (int(t.r * 31.0f + 0.5f) << 0) | + (int(t.g * 31.0f + 0.5f) << 5) | + (int(t.b * 31.0f + 0.5f) << 10) | + (int(t.a) << 15); + return c; +} + +vec4 readTexture(in vec2 pos) { + vec2 apos = vec2(1024.0f, 512.0f) * pos; + vec2 fpos = fract(apos); + ivec2 ipos = ivec2(apos); + vec4 ret = vec4(0.0f); + if (pos.x > 1.0f) return ret; + if (pos.y > 1.0f) return ret; + if (pos.x < 0.0f) return ret; + if (pos.y < 0.0f) return ret; + + float scale = 0.0f; + int p = 0; + vec4 t = texture(u_vramTexture, pos); + int c = texelToRaw(t); + + ret.a = 1.0f; + vec4 tb = texture(u_vramTexture, pos - vec2(1.0 / 1024.0f, 0.0f)); + vec4 ta = texture(u_vramTexture, pos + vec2(1.0 / 1024.0f, 0.0f)); + int cb = texelToRaw(tb); + int ca = texelToRaw(ta); + switch ((ipos.x + u_24shift) % 3) { + case 0: + ret.r = float((c >> 0) & 0xff) / 255.0f; + ret.g = float((c >> 8) & 0xff) / 255.0f; + ret.b = float((ca >> 0) & 0xff) / 255.0f; + break; + case 1: + if (fpos.x < 0.5f) { + ret.r = float((cb >> 0) & 0xff) / 255.0f; + ret.g = float((cb >> 8) & 0xff) / 255.0f; + ret.b = float((c >> 0) & 0xff) / 255.0f; + } else { + ret.r = float((c >> 8) & 0xff) / 255.0f; + ret.g = float((ca >> 0) & 0xff) / 255.0f; + ret.b = float((ca >> 8) & 0xff) / 255.0f; + } + break; + case 2: + ret.r = float((cb >> 8) & 0xff) / 255.0f; + ret.g = float((c >> 0) & 0xff) / 255.0f; + ret.b = float((c >> 8) & 0xff) / 255.0f; + break; + } + + return ret; +} + +void main() { + Out_Color = readTexture(Frag_UV.st); + Out_Color.a = 1.0; +} +)", + R"( +local loc24shift = gl.glGetUniformLocation(shaderProgramID, 'u_24shift') + +function BindAttributes(textureID, shaderProgramID, srcLocX, srcLocY, srcSizeX, srcSizeY, dstSizeX, dstSizeY, shift) + gl.glUniform1i(loc24shift, shift) +end +)"); + status = m_shaderEditor24.compile(m_gui); + if (!status.isOk()) return -1; + reset(); setOpenGLContext(); return 0; @@ -424,9 +523,9 @@ void PCSX::OpenGL_GPU::debug() { if (ImGui::Button(_("Clear VRAM"))) { clearVRAM(m_clearColour.r(), m_clearColour.g(), m_clearColour.b()); } - - ImGui::End(); } + + ImGui::End(); } // Called at the start of a UI frame to restore context @@ -490,23 +589,38 @@ void PCSX::OpenGL_GPU::vblank(bool fromGui) { OpenGL::disableBlend(); } - // We can't draw the MSAA texture directly. So if we're using MSAA, we copy the texture to a non-MSAA texture. - if (m_multisampled) { - m_fbo.bind(OpenGL::ReadFramebuffer); - m_fboNoMSAA.bind(OpenGL::DrawFramebuffer); - glBlitFramebuffer(0, 0, 1024, 512, 0, 0, 1024, 512, GL_COLOR_BUFFER_BIT, GL_LINEAR); + GLuint texture; + if (!m_display.enabled) { + texture = m_blankTexture.handle(); + } else { + // We can't draw the MSAA texture directly. So if we're using MSAA, we copy the texture to a non-MSAA texture. + if (m_multisampled) { + m_fbo.bind(OpenGL::ReadFramebuffer); + m_fboNoMSAA.bind(OpenGL::DrawFramebuffer); + glBlitFramebuffer(0, 0, 1024, 512, 0, 0, 1024, 512, GL_COLOR_BUFFER_BIT, GL_LINEAR); + texture = m_vramTextureNoMSAA.handle(); + } else { + texture = m_vramTexture.handle(); + } } - m_gui->setViewport(); - if (!fromGui) m_gui->flip(); // Set up offscreen framebuffer before rendering - float startX = m_display.startNormalized.x(); float startY = m_display.startNormalized.y(); float width = m_display.sizeNormalized.x(); float height = m_display.sizeNormalized.y(); - m_gui->m_offscreenShaderEditor.render(m_gui, m_displayTexture, {startX, startY}, {width, height}, - m_gui->getRenderSize()); + if (m_display.info.depth == CtrlDisplayMode::CD_24BITS) { + m_fbo24.bind(OpenGL::DrawFramebuffer); + OpenGL::setViewport(1024, 512); + m_shaderEditor24.render(m_gui, texture, {0, 0}, {1, 1}, {1024, 512}, {lua_Number(m_display.start.x() * 2)}); + texture = m_vramTexture24.handle(); + width *= 1.5f; + } + + m_gui->setViewport(); + if (!fromGui) m_gui->flip(); // Set up offscreen framebuffer before rendering + + m_gui->m_offscreenShaderEditor.render(m_gui, texture, {startX, startY}, {width, height}, m_gui->getRenderSize()); } void PCSX::OpenGL_GPU::renderBatch() { @@ -543,14 +657,7 @@ void PCSX::OpenGL_GPU::renderBatch() { } } -void PCSX::OpenGL_GPU::setDisplayEnable(bool setting) { - m_display.enabled = setting; - if (!setting) { - m_displayTexture = m_blankTexture.handle(); - } else { - m_displayTexture = m_multisampled ? m_vramTextureNoMSAA.handle() : m_vramTexture.handle(); - } -} +void PCSX::OpenGL_GPU::setDisplayEnable(bool enabled) { m_display.enabled = enabled; } PCSX::Slice PCSX::OpenGL_GPU::getVRAM(Ownership) { static constexpr uint32_t texSize = 1024 * 512 * sizeof(uint16_t); diff --git a/src/core/OpenGL_GPU/gpu_opengl.h b/src/core/OpenGL_GPU/gpu_opengl.h index e95c67ce9..b2b5283f4 100644 --- a/src/core/OpenGL_GPU/gpu_opengl.h +++ b/src/core/OpenGL_GPU/gpu_opengl.h @@ -103,6 +103,11 @@ class OpenGL_GPU final : public GPU { // For CPU->VRAM texture transfers OpenGL::Texture m_sampleTexture; + // For the 16-bits to 24-bits conversion + OpenGL::Framebuffer m_fbo24; + OpenGL::Texture m_vramTexture24; + Widgets::ShaderEditor m_shaderEditor24 = {"16-to-24"}; + std::vector m_vertices; OpenGL::Rect m_scissorBox; int m_drawAreaLeft, m_drawAreaRight, m_drawAreaTop, m_drawAreaBottom; @@ -123,10 +128,6 @@ class OpenGL_GPU final : public GPU { GLint m_texWindowLoc; GLint m_blendFactorsLoc; GLint m_blendFactorsIfOpaqueLoc; - // The handle of the texture to actually display on screen. - // The handle of either m_vramTexture, m_vramTextureNoMSAA or m_blankTexture - // Depending on whether the display and MSAA are enabled - GLuint m_displayTexture; int m_vertexCount = 0; bool m_updateDrawOffset = false; diff --git a/src/core/callstacks.h b/src/core/callstacks.h index 6fb0195c2..ca6c7f8b5 100644 --- a/src/core/callstacks.h +++ b/src/core/callstacks.h @@ -74,6 +74,7 @@ class CallStacks { m_currentSP = 0; }); } + ~CallStacks() { m_callstacks.destroyAll(); } void setSP(uint32_t oldSP, uint32_t newSP); void offsetSP(uint32_t oldSP, int32_t offset); void storeRA(uint32_t sp, uint32_t ra); diff --git a/src/core/cdrom.h b/src/core/cdrom.h index f7aab26cd..e244980cc 100644 --- a/src/core/cdrom.h +++ b/src/core/cdrom.h @@ -42,7 +42,7 @@ class IsoBrowser; class CDRom { public: using MSF = IEC60908b::MSF; - CDRom() : m_iso(new CDRIso()) {} + CDRom() : m_iso(new CDRIso(new FailedFile)) {} virtual ~CDRom() {} static CDRom* factory(); bool isLidOpen(); diff --git a/src/core/debug.cc b/src/core/debug.cc index 35aa97566..380cd1984 100644 --- a/src/core/debug.cc +++ b/src/core/debug.cc @@ -160,9 +160,11 @@ void PCSX::Debug::process(uint32_t oldPC, uint32_t newPC, uint32_t oldCode, uint } if (m_step == STEP_NONE) return; + bool skipStepOverAndOut = false; if (!m_wasInISR && g_emulator->m_cpu->m_inISR) { uint32_t cause = (regs.CP0.n.Cause >> 2) & 0x1f; if (cause == 0) return; + skipStepOverAndOut = true; } switch (m_step) { @@ -170,7 +172,7 @@ void PCSX::Debug::process(uint32_t oldPC, uint32_t newPC, uint32_t oldCode, uint triggerBP(nullptr, newPC, 4, _("Step in")); } break; case STEP_OVER: { - if (!m_stepperHasBreakpoint) { + if (!m_stepperHasBreakpoint && !skipStepOverAndOut) { if (linked) { uint32_t sp = regs.GPR.n.sp; m_stepperHasBreakpoint = true; @@ -188,7 +190,10 @@ void PCSX::Debug::process(uint32_t oldPC, uint32_t newPC, uint32_t oldCode, uint } } break; case STEP_OUT: { - if (!m_stepperHasBreakpoint) triggerBP(nullptr, newPC, 4, _("Step out (no callstack)")); + if (!m_stepperHasBreakpoint && !skipStepOverAndOut) { + triggerBP(nullptr, newPC, 4, _("Step out (no callstack)")); + } + break; } } } diff --git a/src/core/disr3000a.cc b/src/core/disr3000a.cc index 1b492f332..8c2099b6a 100644 --- a/src/core/disr3000a.cc +++ b/src/core/disr3000a.cc @@ -267,7 +267,7 @@ declare(disADDI) { declare(disADDIU) { if (_Rs_ == 0) { // this is the common pseudo-instruction to load an immediate 16 bits value - dOpCode("move"); + dOpCode("li"); GPR(_Rt_); } else { dOpCode("addiu"); @@ -285,7 +285,7 @@ declare(disANDI) { declare(disORI) { if (_Rs_ == 0) { // while rare, this can also be used to load an immediate 16-bits value - dOpCode("move"); + dOpCode("li"); GPR(_Rt_); } else { dOpCode("ori"); diff --git a/src/core/gpu.cc b/src/core/gpu.cc index c1c0fa82b..f172be7ee 100644 --- a/src/core/gpu.cc +++ b/src/core/gpu.cc @@ -462,6 +462,8 @@ uint32_t PCSX::GPU::readStatus() { if ((ret & GPUSTATUS_IDLE) == 0) ret &= ~GPUSTATUS_READYFORVRAM; #endif if (m_readFifo->size() != 0) ret |= GPUSTATUS_READYFORVRAM; + // Let's pretend our input fifo is always ready for more data. + if ((ret & 0x60000000) == 0x20000000) ret |= 0x02000000; return ret; } diff --git a/src/core/isoffi.lua b/src/core/isoffi.lua index 96d7b11fd..fecd19718 100644 --- a/src/core/isoffi.lua +++ b/src/core/isoffi.lua @@ -35,6 +35,8 @@ bool isIsoFailed(LuaIso* wrapper); void isoClearPPF(LuaIso* wrapper); void isoSavePPF(LuaIso* wrapper); LuaIso* getCurrentIso(); +LuaIso* openIso(const char* path); +LuaIso* openIsoFromFile(LuaFile* wrapper); IsoReader* createIsoReader(LuaIso* wrapper); void deleteIsoReader(IsoReader* isoReader); @@ -109,6 +111,62 @@ local function createIsoBuilderWrapper(wrapper) end PCSX.getCurrentIso = function() return createIsoWrapper(C.getCurrentIso()) end +PCSX.openIso = function(arg) + if type(arg) == 'string' then + return createIsoWrapper(C.openIso(arg)) + else + return createIsoWrapper(C.openIsoFromFile(arg._wrapper)) + end +end PCSX.isoBuilder = function(file) return createIsoBuilderWrapper(C.createIsoBuilder(file._wrapper)) end +PCSX.isoTools = { + fromBCD = function(bcd) + local dec = 0 + local mul = 1 + while bcd ~= 0 do + local digit = bcd % 16 + if digit >= 10 then + error('Invalid BCD digit: ' .. digit) + end + dec = dec + mul * digit + mul = mul * 10 + bcd = math.floor(bcd / 16) + end + return dec + end, + toBCD = function(dec) + local bcd = 0 + local mul = 1 + while dec ~= 0 do + local digit = dec % 10 + bcd = bcd + mul * digit + mul = mul * 16 + dec = math.floor(dec / 10) + end + return bcd + end, + fromMSF = function(m, s, f) + m = PCSX.isoTools.fromBCD(m) + s = PCSX.isoTools.fromBCD(s) + f = PCSX.isoTools.fromBCD(f) + if s >= 60 then + error('Invalid MSF seconds: ' .. s) + end + if f >= 75 then + error('Invalid MSF frames: ' .. f) + end + return (m * 60 + s) * 75 + f - 150 + end, + toMSF = function(lba) + lba = lba + 150 + local f = lba % 75 + lba = lba - f + lba = lba / 75 + local s = lba % 60 + lba = lba - s + local m = lba / 60 + return m, s, f + end, +} -- )EOF" diff --git a/src/core/luaiso.cc b/src/core/luaiso.cc index 7f4c5c30b..3c0266ded 100644 --- a/src/core/luaiso.cc +++ b/src/core/luaiso.cc @@ -46,6 +46,10 @@ bool isIsoFailed(LuaIso* wrapper) { return wrapper->iso->failed(); } void isoClearPPF(LuaIso* wrapper) { wrapper->iso->getPPF()->clear(); } void isoSavePPF(LuaIso* wrapper) { wrapper->iso->getPPF()->save(wrapper->iso->getIsoPath()); } LuaIso* getCurrentIso() { return new LuaIso(PCSX::g_emulator->m_cdrom->getIso()); } +LuaIso* openIso(const char* path) { return new LuaIso(std::make_shared(path)); } +LuaIso* openIsoFromFile(PCSX::LuaFFI::LuaFile* wrapper) { + return new LuaIso(std::make_shared(wrapper->file)); +} PCSX::ISO9660Reader* createIsoReader(LuaIso* wrapper) { return new PCSX::ISO9660Reader(wrapper->iso); } void deleteIsoReader(PCSX::ISO9660Reader* isoReader) { delete isoReader; } @@ -96,6 +100,8 @@ static void registerAllSymbols(PCSX::Lua L) { REGISTER(L, isoClearPPF); REGISTER(L, isoSavePPF); REGISTER(L, getCurrentIso); + REGISTER(L, openIso); + REGISTER(L, openIsoFromFile); REGISTER(L, getIsoTD); REGISTER(L, getIsoTN); REGISTER(L, createIsoReader); diff --git a/src/core/pad.cc b/src/core/pad.cc index 2b5995043..371154082 100644 --- a/src/core/pad.cc +++ b/src/core/pad.cc @@ -1238,50 +1238,6 @@ void PadsImpl::Pad::setDefaults(bool firstController) { } void PadsImpl::setLua(PCSX::Lua L) { - auto getButton = [this](PCSX::Lua L, unsigned pad) -> int { - int n = L.gettop(); - if (n == 0) { - return L.error("Not enough arguments to getButton"); - } - if (!L.isnumber(1)) { - return L.error("Invalid argument to getButton"); - } - auto buttons = m_pads[pad].m_data.buttonStatus; - unsigned button = L.checknumber(1); - L.push((buttons & (1 << button)) == 0); - return 1; - }; - - auto setOverride = [this](PCSX::Lua L, unsigned pad) -> int { - int n = L.gettop(); - if (n == 0) { - return L.error("Not enough arguments to setOverride"); - } - if (!L.isnumber(1)) { - return L.error("Invalid argument to setOverride"); - } - auto& overrides = m_pads[pad].m_data.overrides; - unsigned button = L.checknumber(1); - button = 1 << button; - overrides &= ~button; - return 0; - }; - - auto clearOverride = [this](PCSX::Lua L, unsigned pad) -> int { - int n = L.gettop(); - if (n == 0) { - return L.error("Not enough arguments to clearOverride"); - } - if (!L.isnumber(1)) { - return L.error("Invalid argument to clearOverride"); - } - auto& overrides = m_pads[pad].m_data.overrides; - unsigned button = L.checknumber(1); - button = 1 << button; - overrides |= button; - return 0; - }; - L.getfieldtable("PCSX", LUA_GLOBALSINDEX); // setting constants @@ -1326,12 +1282,13 @@ void PadsImpl::setLua(PCSX::Lua L) { L.getfieldtable("settings"); L.getfieldtable("pads"); - L.push(lua_Number(1)); - m_pads[0].m_settings.pushValue(L); - L.settable(); - L.push(lua_Number(2)); - m_pads[0].m_settings.pushValue(L); - L.settable(); + auto pushSettings = [this, L](unsigned pad) mutable { + L.push(lua_Number(pad + 1)); + m_pads[pad].m_settings.pushValue(L); + L.settable(); + }; + pushSettings(0); + pushSettings(1); L.pop(); L.pop(); @@ -1340,37 +1297,79 @@ void PadsImpl::setLua(PCSX::Lua L) { // pads callbacks - L.getfieldtable(1); - L.getfieldtable("pads"); - L.getfieldtable(1); - - // push first pad stuff here - L.declareFunc( - "getButton", [getButton](PCSX::Lua L) -> int { return getButton(L, 0); }, -1); - L.declareFunc( - "setOverride", [setOverride](PCSX::Lua L) -> int { return setOverride(L, 0); }, -1); - L.declareFunc( - "clearOverride", [clearOverride](PCSX::Lua L) -> int { return clearOverride(L, 0); }, -1); - - L.pop(); - L.pop(); - L.pop(); - - L.getfieldtable(2); - L.getfieldtable("pads"); - L.getfieldtable(1); + auto setCallbacks = [this, L](unsigned pad) mutable { + L.getfieldtable(pad + 1); + L.getfieldtable("pads"); + L.getfieldtable(1); - // push second pad stuff here - L.declareFunc( - "getButton", [getButton](PCSX::Lua L) -> int { return getButton(L, 1); }, -1); - L.declareFunc( - "setOverride", [setOverride](PCSX::Lua L) -> int { return setOverride(L, 1); }, -1); - L.declareFunc( - "clearOverride", [clearOverride](PCSX::Lua L) -> int { return clearOverride(L, 1); }, -1); + L.declareFunc( + "getButton", [this, pad](PCSX::Lua L) -> int { + int n = L.gettop(); + if (n == 0) { + return L.error("Not enough arguments to getButton"); + } + if (!L.isnumber(1)) { + return L.error("Invalid argument to getButton"); + } + auto buttons = m_pads[pad].m_data.buttonStatus; + auto overrides = m_pads[pad].m_data.overrides; + unsigned button = L.checknumber(1); + L.push(((overrides & buttons) & (1 << button)) == 0); + return 1; + }, -1); + L.declareFunc( + "setOverride", [this, pad](PCSX::Lua L) -> int { + int n = L.gettop(); + if (n == 0) { + return L.error("Not enough arguments to setOverride"); + } + if (!L.isnumber(1)) { + return L.error("Invalid argument to setOverride"); + } + auto& overrides = m_pads[pad].m_data.overrides; + unsigned button = L.checknumber(1); + button = 1 << button; + overrides &= ~button; + return 0; + }, -1); + L.declareFunc( + "clearOverride", [this, pad](PCSX::Lua L) -> int { + int n = L.gettop(); + if (n == 0) { + return L.error("Not enough arguments to clearOverride"); + } + if (!L.isnumber(1)) { + return L.error("Invalid argument to clearOverride"); + } + auto& overrides = m_pads[pad].m_data.overrides; + unsigned button = L.checknumber(1); + button = 1 << button; + overrides |= button; + return 0; + }, -1); + L.declareFunc( + "setAnalogMode", [this, pad](PCSX::Lua L) -> int { + int n = L.gettop(); + if (n == 0) { + m_pads[pad].m_analogMode = false; + } else { + m_pads[pad].m_analogMode = L.toboolean(); + } + return 0; + }, -1); + L.declareFunc( + "map", [this, pad](PCSX::Lua L) -> int { + m_pads[pad].map(); + return 0; + }, -1); + + L.pop(); + L.pop(); + L.pop(); + }; - L.pop(); - L.pop(); - L.pop(); + setCallbacks(0); + setCallbacks(1); L.pop(); L.pop(); diff --git a/src/core/pad.h b/src/core/pad.h index 4bd5c6468..ba0801a7a 100644 --- a/src/core/pad.h +++ b/src/core/pad.h @@ -34,6 +34,8 @@ class Pads { public: enum class Port { Port1 = 0, Port2 }; + virtual ~Pads() = default; + virtual void init() = 0; virtual void shutdown() = 0; virtual uint8_t startPoll(Port port) = 0; diff --git a/src/core/pcsxffi.lua b/src/core/pcsxffi.lua index ff0c9f825..f0a0573ca 100644 --- a/src/core/pcsxffi.lua +++ b/src/core/pcsxffi.lua @@ -88,7 +88,7 @@ void loadSaveStateFromFile(LuaFile*); LuaFile* getMemoryAsFile(); -void quit(); +void quit(int code); ]] local C = ffi.load 'PCSX' @@ -183,7 +183,7 @@ PCSX = { softResetEmulator = function() C.softResetEmulator() end, hardResetEmulator = function() C.hardResetEmulator() end, invalidateCache = function() C.invalidateCache() end, - log = function(...) printLike(C.luaLog, ...) end, + log = function(...) printLike(function(msg) C.luaLog(msg .. '\n') end, ...) end, GUI = { jumpToPC = jumpToPC, jumpToMemory = jumpToMemory }, nextTick = function(f) local oldCleanup = AfterPollingCleanup @@ -212,8 +212,8 @@ PCSX = { error('loadSaveState: requires a Slice or File as input') end end, - getMemoryAsFile = function() return C.getMemoryAsFile() end, - quit = function() C.quit() end, + getMemoryAsFile = function() return Support.File._createFileWrapper(C.getMemoryAsFile()) end, + quit = function(code) C.quit(code or 0) end, } print = function(...) printLike(function(s) C.luaMessage(s, false) end, ...) end diff --git a/src/core/pcsxlua.cc b/src/core/pcsxlua.cc index a5fa9c796..8d25ece1c 100644 --- a/src/core/pcsxlua.cc +++ b/src/core/pcsxlua.cc @@ -120,7 +120,7 @@ PCSX::LuaFFI::LuaFile* getMemoryAsFile() { return new PCSX::LuaFFI::LuaFile(PCSX::g_emulator->m_mem->getMemoryAsFile()); } -void quit() { PCSX::g_system->quit(); } +void quit(int code) { PCSX::g_system->quit(code); } } // namespace diff --git a/src/core/pio-cart.cc b/src/core/pio-cart.cc index 66bb6242a..26e0f124d 100644 --- a/src/core/pio-cart.cc +++ b/src/core/pio-cart.cc @@ -42,13 +42,13 @@ void PCSX::PIOCart::setLuts() { m_readLUT[0x1f05] = &m_exp1[1 << 16]; m_pal.reset(); } else { - memset(&m_readLUT[0x1f00], NULL, 0x6 * sizeof(void *)); + memset(&m_readLUT[0x1f00], 0, 0x6 * sizeof(void *)); } - // NULL by default, wipe to ensure writes are properly intercepted - memset(&m_writeLUT[0x1f00], NULL, 0x6 * sizeof(void *)); - memset(&m_writeLUT[0x9f00], NULL, 0x6 * sizeof(void *)); - memset(&m_writeLUT[0xbf00], NULL, 0x6 * sizeof(void *)); + // nullptr by default, wipe to ensure writes are properly intercepted + memset(&m_writeLUT[0x1f00], 0, 0x6 * sizeof(void *)); + memset(&m_writeLUT[0x9f00], 0, 0x6 * sizeof(void *)); + memset(&m_writeLUT[0xbf00], 0, 0x6 * sizeof(void *)); memcpy(&m_readLUT[0x9f00], &m_readLUT[0x1f00], 0x6 * sizeof(void *)); memcpy(&m_readLUT[0xbf00], &m_readLUT[0x1f00], 0x6 * sizeof(void *)); @@ -133,7 +133,7 @@ void PCSX::PIOCart::PAL::setLUTFlashBank(uint8_t bank) { const auto &m_readLUT = g_emulator->m_mem->m_readLUT; const auto &m_exp1 = g_emulator->m_mem->m_exp1; - if (m_readLUT == NULL || m_exp1 == NULL) return; + if (m_readLUT == nullptr || m_exp1 == nullptr) return; switch (bank) { case 0: diff --git a/src/core/pio-cart.h b/src/core/pio-cart.h index e6b359df4..1c735d736 100644 --- a/src/core/pio-cart.h +++ b/src/core/pio-cart.h @@ -32,12 +32,7 @@ namespace PCSX { class PIOCart { public: PIOCart() : m_pal(this) { - m_detachedMemory = static_cast(calloc(64 * 1024, 1)); - if (m_detachedMemory == NULL) { - g_system->message("%s", _("Error allocating memory!")); - } else { - memset(m_detachedMemory, 0xff, static_cast(64 * 1024)); - } + memset(m_detachedMemory, 0xff, sizeof(m_detachedMemory)); } void setLuts(); @@ -80,7 +75,7 @@ class PIOCart { private: bool m_switchOn = true; - uint8_t *m_detachedMemory = NULL; + uint8_t m_detachedMemory[64 * 1024]; class PAL { public: @@ -101,14 +96,9 @@ class PIOCart { class FlashMemory { public: FlashMemory(PAL *parent) : m_pal(parent) { - m_softwareID = static_cast(calloc(64 * 1024, 1)); - if (m_softwareID == NULL) { - g_system->message("%s", _("Error allocating memory!")); - } else { - for (int i = 0; i < (64 * 1024) - 1; i += 2) { - m_softwareID[i] = 0xbf; - m_softwareID[i + 1] = 0x10; - } + for (int i = 0; i < (64 * 1024) - 1; i += 2) { + m_softwareID[i] = 0xbf; + m_softwareID[i + 1] = 0x10; } } @@ -158,7 +148,7 @@ class PIOCart { uint8_t m_commandBuffer[m_bufferSize] = {0}; uint8_t m_busCycle = 0; - uint8_t *m_softwareID = NULL; + uint8_t m_softwareID[64 * 1024]; bool m_dataProtectEnabled = true; bool m_pageWriteEnabled = false; diff --git a/src/core/psxemulator.cc b/src/core/psxemulator.cc index f3c375d93..5bb66a9e3 100644 --- a/src/core/psxemulator.cc +++ b/src/core/psxemulator.cc @@ -42,6 +42,7 @@ #include "lua/luafile.h" #include "lua/luawrapper.h" #include "lua/zlibffi.h" +#include "luafilesystem/src/lfs.h" extern "C" { #include "luv/src/luv.h" } @@ -49,6 +50,8 @@ extern "C" { #include "supportpsx/assembler.h" #include "supportpsx/binlua.h" +extern "C" int luaopen_lpeg(lua_State* L); + PCSX::Emulator::Emulator() : m_callStacks(new PCSX::CallStacks), m_cdrom(PCSX::CDRom::factory()), @@ -91,6 +94,10 @@ void PCSX::Emulator::setLua() { L.push("luv"); luaopen_luv(L.getState()); L.settable(LUA_GLOBALSINDEX); + luaopen_lfs(L.getState()); + L.pop(3); + luaopen_lpeg(L.getState()); + L.pop(2); LuaFFI::open_file(L); LuaFFI::open_pcsx(L); LuaFFI::open_iso(L); diff --git a/src/core/psxmem.cc b/src/core/psxmem.cc index 6459bb4e9..863691c0b 100644 --- a/src/core/psxmem.cc +++ b/src/core/psxmem.cc @@ -185,6 +185,9 @@ Set a BIOS file into the configuration, and do a hard reset of the emulator. The distributed OpenBIOS.bin file can be an appropriate BIOS replacement. )")); + uint32_t nobioscrc = crc32(0L, Z_NULL, 0); + nobioscrc = crc32(nobioscrc, m_bios, bios_size); + // Load BIOS { auto &biosPath = g_emulator->settings.get().value; @@ -230,7 +233,7 @@ The distributed OpenBIOS.bin file can be an appropriate BIOS replacement. g_system->printf(_("Known BIOS detected: %s (%08x)\n"), it->second, crc); } else if (strncmp((const char *)&m_bios[0x78], "OpenBIOS", 8) == 0) { g_system->printf(_("OpenBIOS detected (%08x)\n"), crc); - } else { + } else if (crc != nobioscrc) { g_system->printf(_("Unknown bios loaded (%08x)\n"), crc); } } diff --git a/src/core/r3000a.cc b/src/core/r3000a.cc index 9924b1e1e..dd7836307 100644 --- a/src/core/r3000a.cc +++ b/src/core/r3000a.cc @@ -57,6 +57,15 @@ void PCSX::R3000Acpu::psxReset() { memset(&m_regs, 0, sizeof(m_regs)); m_shellStarted = false; + m_inISR = false; + m_nextIsDelaySlot = false; + m_inDelaySlot = false; + m_delayedLoadInfo[0].active = false; + m_delayedLoadInfo[0].pcActive = false; + m_delayedLoadInfo[1].active = false; + m_delayedLoadInfo[1].pcActive = false; + m_currentDelayedLoad = false; + closeAllPCdrvFiles(); m_regs.pc = 0xbfc00000; // Start in bootstrap @@ -80,16 +89,17 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) { IO memFile = g_emulator->m_mem->getMemoryAsFile(); uint32_t code = (memFile->readAt(m_regs.pc) >> 6) & 0xfffff; auto& regs = m_regs.GPR.n; + uint16_t fd = 0; switch (code) { case 0x101: { // PCinit - closeAllPCdevFiles(); + closeAllPCdrvFiles(); regs.v0 = 0; regs.v1 = 0; m_regs.pc += 4; return; } case 0x102: { // PCcreat - if (m_pcdrvFiles.size() > std::numeric_limits::max()) { + if (m_availableFDs.empty()) { regs.v0 = -1; regs.v1 = -1; m_regs.pc += 4; @@ -98,26 +108,23 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) { std::filesystem::path basepath = debugSettings.get(); memFile->rSeek(m_regs.GPR.n.a0); auto filename = memFile->gets(); - PCdrvFiles::iterator file; - do { - file = m_pcdrvFiles.find(++m_pcdrvIndex); - } while (file != m_pcdrvFiles.end()); - file = m_pcdrvFiles.insert(m_pcdrvIndex, new PCdrvFile(basepath / filename, FileOps::TRUNCATE)); + fd = m_availableFDs.front(); + auto file = m_pcdrvFiles.insert(fd, new PCdrvFile(basepath / filename, FileOps::TRUNCATE)); file->m_relativeFilename = filename; - if (file->failed()) { + if ((*file)->failed()) { regs.v0 = -1; regs.v1 = -1; - file->close(); delete &*file; } else { + m_availableFDs.pop_front(); regs.v0 = 0; - regs.v1 = file->getKey(); + regs.v1 = fd; } m_regs.pc += 4; return; } case 0x103: { // PCopen - if (m_pcdrvFiles.size() > std::numeric_limits::max()) { + if (m_availableFDs.empty()) { regs.v0 = -1; regs.v1 = -1; m_regs.pc += 4; @@ -127,38 +134,37 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) { memFile->rSeek(m_regs.GPR.n.a0); auto filename = memFile->gets(); PCdrvFiles::iterator file; - do { - file = m_pcdrvFiles.find(++m_pcdrvIndex); - } while (file != m_pcdrvFiles.end()); auto path = basepath / filename; + fd = m_availableFDs.front(); if (regs.a1 == 0) { - file = m_pcdrvFiles.insert(m_pcdrvIndex, new PCdrvFile(path)); + file = m_pcdrvFiles.insert(fd, new PCdrvFile(path)); } else { - file = m_pcdrvFiles.insert(m_pcdrvIndex, new PCdrvFile(path, FileOps::READWRITE)); + file = m_pcdrvFiles.insert(fd, new PCdrvFile(path, FileOps::READWRITE)); } file->m_relativeFilename = filename; - if (file->failed()) { + if ((*file)->failed()) { regs.v0 = -1; regs.v1 = -1; - file->close(); delete &*file; } else { + m_availableFDs.pop_front(); regs.v0 = 0; - regs.v1 = file->getKey(); + regs.v1 = fd; } m_regs.pc += 4; return; } case 0x104: { // PCclose - auto file = m_pcdrvFiles.find(m_regs.GPR.n.a0); + fd = m_regs.GPR.n.a0; + auto file = m_pcdrvFiles.find(fd); if (file == m_pcdrvFiles.end()) { regs.v0 = -1; regs.v1 = -1; } else { regs.v0 = 0; regs.v1 = 0; - file->close(); delete &*file; + m_availableFDs.push_back(fd); } m_regs.pc += 4; return; @@ -171,13 +177,13 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) { m_regs.pc += 4; return; } - if (file->failed() || file->eof()) { + if ((*file)->failed() || (*file)->eof()) { regs.v0 = -1; regs.v1 = -1; m_regs.pc += 4; return; } - auto slice = static_cast(&*file)->read(regs.a2); + auto slice = (*file)->read(regs.a2); regs.v0 = 0; regs.v1 = slice.size(); memFile->writeAt(std::move(slice), regs.a3); @@ -193,7 +199,7 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) { return; } auto slice = memFile->readAt(regs.a2, regs.a3); - if ((regs.v1 = file->write(slice.data(), slice.size())) < 0) { + if ((regs.v1 = (*file)->write(slice.data(), slice.size())) < 0) { regs.v0 = -1; } else { regs.v0 = 0; @@ -226,10 +232,10 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) { m_regs.pc += 4; return; } - auto ret = file->writable() ? file->wSeek(regs.a2, wheel) : file->rSeek(regs.a2, wheel); + auto ret = (*file)->writable() ? (*file)->wSeek(regs.a2, wheel) : (*file)->rSeek(regs.a2, wheel); if (ret >= 0) { regs.v0 = 0; - regs.v1 = file->writable() ? file->wTell() : file->rTell(); + regs.v1 = (*file)->writable() ? (*file)->wTell() : (*file)->rTell(); } else { regs.v0 = -1; regs.v1 = ret; @@ -255,9 +261,9 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) { // Set the EPC & PC if (bd) { code |= 0x80000000; - m_regs.CP0.n.EPC = (m_regs.pc - 4); + m_regs.CP0.n.EPC = m_regs.pc - 4; } else { - m_regs.CP0.n.EPC = (m_regs.pc); + m_regs.CP0.n.EPC = m_regs.pc; } if (m_regs.CP0.n.Status & 0x400000) { @@ -279,6 +285,12 @@ void PCSX::R3000Acpu::restorePCdrvFile(const std::filesystem::path& filename, ui auto& debugSettings = emuSettings.get(); std::filesystem::path basepath = debugSettings.get(); m_pcdrvFiles.insert(fd, new PCdrvFile(basepath / filename)); + for (auto f = m_availableFDs.begin(); f != m_availableFDs.end(); f++) { + if (*f == fd) { + m_availableFDs.erase(f); + break; + } + } } void PCSX::R3000Acpu::restorePCdrvFile(const std::filesystem::path& filename, uint16_t fd, FileOps::Create) { @@ -286,8 +298,14 @@ void PCSX::R3000Acpu::restorePCdrvFile(const std::filesystem::path& filename, ui auto& debugSettings = emuSettings.get(); std::filesystem::path basepath = debugSettings.get(); auto f = new PCdrvFile(basepath / filename, FileOps::CREATE); - f->wSeek(0, SEEK_END); + (*f)->wSeek(0, SEEK_END); m_pcdrvFiles.insert(fd, f); + for (auto f = m_availableFDs.begin(); f != m_availableFDs.end(); f++) { + if (*f == fd) { + m_availableFDs.erase(f); + break; + } + } } void PCSX::R3000Acpu::branchTest() { diff --git a/src/core/r3000a.h b/src/core/r3000a.h index 421a5ae3a..00953d07c 100644 --- a/src/core/r3000a.h +++ b/src/core/r3000a.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -257,7 +258,12 @@ struct psxRegisters { class R3000Acpu { public: - virtual ~R3000Acpu() { closeAllPCdevFiles(); } + virtual ~R3000Acpu() { m_pcdrvFiles.destroyAll(); }; + R3000Acpu() { + for (unsigned i = 0; i < 65536; i++) { + m_availableFDs.push_back(i); + } + } virtual bool Init() { return false; } virtual void Execute() = 0; /* executes up to a debug break */ virtual void Clear(uint32_t Addr, uint32_t Size) = 0; @@ -272,7 +278,6 @@ class R3000Acpu { std::map m_symbols; - public: static int psxInit(); virtual bool isDynarec() = 0; void psxReset(); @@ -496,26 +501,29 @@ Formula One 2001 struct PCdrvFile; typedef Intrusive::HashTable PCdrvFiles; - struct PCdrvFile : public PosixFile, public PCdrvFiles::Node { - PCdrvFile(const std::filesystem::path &filename) : PosixFile(filename) {} + struct PCdrvFile : public IO, public PCdrvFiles::Node { + PCdrvFile(const std::filesystem::path &filename) : IO(new PosixFile(filename)) {} PCdrvFile(const std::filesystem::path &filename, FileOps::ReadWrite) - : PosixFile(filename, FileOps::READWRITE) {} - PCdrvFile(const std::filesystem::path &filename, FileOps::Truncate) : PosixFile(filename, FileOps::TRUNCATE) {} - PCdrvFile(const std::filesystem::path &filename, FileOps::Create) : PosixFile(filename, FileOps::CREATE) {} + : IO(new PosixFile(filename, FileOps::READWRITE)) {} + PCdrvFile(const std::filesystem::path &filename, FileOps::Truncate) : IO(new PosixFile(filename, FileOps::TRUNCATE)) {} + PCdrvFile(const std::filesystem::path &filename, FileOps::Create) : IO(new PosixFile(filename, FileOps::CREATE)) {} virtual ~PCdrvFile() = default; std::string m_relativeFilename; }; PCdrvFiles m_pcdrvFiles; - uint16_t m_pcdrvIndex = 0; + std::list m_availableFDs; public: - void closeAllPCdevFiles() { - for (auto &f : m_pcdrvFiles) f.close(); + void closeAllPCdrvFiles() { m_pcdrvFiles.destroyAll(); + m_availableFDs.clear(); + for (unsigned i = 0; i < 65536; i++) { + m_availableFDs.push_back(i); + } } - void listAllPCdevFiles(std::function walker) { + void listAllPCdrvFiles(std::function walker) { for (auto iter = m_pcdrvFiles.begin(); iter != m_pcdrvFiles.end(); iter++) { - walker(iter->getKey(), iter->m_relativeFilename, iter->writable()); + walker(iter->getKey(), iter->m_relativeFilename, (*iter)->writable()); } } void restorePCdrvFile(const std::filesystem::path &path, uint16_t fd); diff --git a/src/core/sstate.cc b/src/core/sstate.cc index c04b4be90..bd9e95f1a 100644 --- a/src/core/sstate.cc +++ b/src/core/sstate.cc @@ -137,7 +137,7 @@ std::string PCSX::SaveStates::save() { g_emulator->m_counters->serialize(&wrapper); g_emulator->m_mdec->serialize(&wrapper); - g_emulator->m_cpu->listAllPCdevFiles([&state](uint16_t fd, std::filesystem::path filename, bool create) { + g_emulator->m_cpu->listAllPCdrvFiles([&state](uint16_t fd, std::filesystem::path filename, bool create) { state.get().value.emplace_back(fd, filename.string(), create); }); @@ -272,7 +272,7 @@ bool PCSX::SaveStates::load(std::string_view data) { g_emulator->m_spu->playADPCMchannel(&g_emulator->m_cdrom->m_xa); #endif - g_emulator->m_cpu->closeAllPCdevFiles(); + g_emulator->m_cpu->closeAllPCdrvFiles(); for (auto& file : state.get().value) { uint16_t fd = file.get().value; std::string filename = file.get().value; diff --git a/src/core/system.h b/src/core/system.h index a23e4cecb..15ca820d8 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -259,7 +259,7 @@ class System { protected: std::filesystem::path m_binDir; PCSX::VersionInfo m_version; - bool m_emergencyExit = true; + bool m_emergencyExit = false; }; extern System *g_system; diff --git a/src/core/ui.cc b/src/core/ui.cc index 96e91de1a..120c737c4 100644 --- a/src/core/ui.cc +++ b/src/core/ui.cc @@ -23,6 +23,7 @@ #include "core/callstacks.h" #include "core/debug.h" +#include "core/gpu.h" #include "core/pad.h" #include "core/psxemulator.h" #include "core/spu.h" @@ -93,7 +94,12 @@ void PCSX::UI::tick() { void PCSX::UI::shellReached() { auto& regs = g_emulator->m_cpu->m_regs; uint32_t oldPC = regs.pc; - if (g_emulator->settings.get()) regs.pc = regs.GPR.n.ra; + if (g_emulator->settings.get()) { + regs.pc = regs.GPR.n.ra; + // Enables display as some games like SaGa Frontier (USA) + // rely on the side effect of the shell to do that for them. + g_emulator->m_gpu->writeStatus(0x03000000); + } if (m_exeToLoad.empty()) return; PCSX::u8string filename = m_exeToLoad.get(); diff --git a/src/gpu/soft/gpu.cc b/src/gpu/soft/gpu.cc index 43d0f9d73..d17a8c570 100644 --- a/src/gpu/soft/gpu.cc +++ b/src/gpu/soft/gpu.cc @@ -275,8 +275,8 @@ void PCSX::SoftGPU::impl::debug() { _("Debugging features are not supported when using the software renderer yet\nConsider enabling the " "OpenGL " "GPU option instead.")); - ImGui::End(); } + ImGui::End(); } static constexpr inline uint16_t BGR24to16(uint32_t BGR) { diff --git a/src/gpu/soft/soft.cc b/src/gpu/soft/soft.cc index 03c6f3f05..a5fc4415b 100644 --- a/src/gpu/soft/soft.cc +++ b/src/gpu/soft/soft.cc @@ -152,12 +152,12 @@ void PCSX::SoftGPU::SoftRenderer::texturePage(GPU::TPage *prim) { m_globalTextAddrY = prim->ty << 8; if (m_useDither == 2) { - m_ditherMode = 2; + m_ditherMode = true; } else { if (prim->dither) { - m_ditherMode = m_useDither; + m_ditherMode = m_useDither != 0; } else { - m_ditherMode = 0; + m_ditherMode = false; } } @@ -3752,7 +3752,7 @@ void PCSX::SoftGPU::SoftRenderer::drawPoly3Gi(int16_t x1, int16_t y1, int16_t x2 const auto setMask16 = m_setMask16; const auto setMask32 = m_setMask32; - if (!m_checkMask && !m_drawSemiTrans && m_ditherMode != 2) { + if (!m_checkMask && !m_drawSemiTrans && !m_ditherMode) { for (i = ymin; i <= ymax; i++) { xmin = (m_leftX >> 16); xmax = (m_rightX >> 16) - 1; @@ -3792,7 +3792,7 @@ void PCSX::SoftGPU::SoftRenderer::drawPoly3Gi(int16_t x1, int16_t y1, int16_t x2 return; } - if (m_ditherMode == 2) { + if (m_ditherMode) { for (i = ymin; i <= ymax; i++) { xmin = (m_leftX >> 16); xmax = (m_rightX >> 16) - 1; @@ -3939,7 +3939,7 @@ void PCSX::SoftGPU::SoftRenderer::drawPoly3TGEx4i(int16_t x1, int16_t y1, int16_ const auto setMask32 = m_setMask32; const auto ditherMode = m_ditherMode; - if (!m_checkMask && !m_drawSemiTrans && !m_ditherMode) { + if (!m_checkMask && !m_drawSemiTrans && !ditherMode) { for (i = ymin; i <= ymax; i++) { xmin = ((m_leftX) >> 16); xmax = ((m_rightX) >> 16) - 1; //!!!!!!!!!!!!! diff --git a/src/gpu/soft/soft.h b/src/gpu/soft/soft.h index 2b421ee72..aea4cfe6a 100644 --- a/src/gpu/soft/soft.h +++ b/src/gpu/soft/soft.h @@ -81,15 +81,15 @@ struct SoftRenderer { int32_t PAL; int32_t InterlacedNew; int32_t Interlaced; - int32_t RGB24New; - int32_t RGB24; + bool RGB24New; + bool RGB24; ShortPoint DrawOffset; int32_t Disabled; SoftRect Range; }; SoftRect m_textureWindow; - int m_ditherMode = 0; + bool m_ditherMode = false; int m_drawX, m_drawY, m_drawW, m_drawH; static constexpr int GPU_WIDTH = 1024; diff --git a/src/gui/gui.cc b/src/gui/gui.cc index 67a188bac..9d4888e93 100644 --- a/src/gui/gui.cc +++ b/src/gui/gui.cc @@ -30,6 +30,11 @@ #include #include #include +extern "C" { +#include +#include +#include +} #include #include @@ -1048,7 +1053,7 @@ void PCSX::GUI::endFrame() { if (ImGui::BeginMenu(_("File"))) { showOpenIsoFileDialog = ImGui::MenuItem(_("Open Disk Image")); if (ImGui::MenuItem(_("Close Disk Image"))) { - PCSX::g_emulator->m_cdrom->setIso(new CDRIso()); + PCSX::g_emulator->m_cdrom->setIso(new CDRIso(new FailedFile)); PCSX::g_emulator->m_cdrom->parseIso(); } if (ImGui::MenuItem(_("Load binary"))) { @@ -1779,8 +1784,6 @@ the update and manually apply it.)"))); while (L.gettop()) L.pop(); } - glfwSwapBuffers(m_window); - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { ImGui::UpdatePlatformWindows(); // Skip the main viewport (index 0), which is always fully handled by the application! @@ -1791,38 +1794,42 @@ the update and manually apply it.)"))); if (platform_io.Renderer_RenderWindow) platform_io.Renderer_RenderWindow(viewport, nullptr); if (vg) { auto window = getGLFWwindowFromImGuiViewport(viewport); - glfwGetWindowSize(window, &winWidth, &winHeight); - glfwGetFramebufferSize(window, &fbWidth, &fbHeight); - pxRatio = (float)fbWidth / (float)winWidth; - nvgSwitchSubContextGL(vg, m_nvgSubContextes[viewport->ID]); - nvgBeginFrame(vg, winWidth, winHeight, pxRatio); - L.getfieldtable("nvg", LUA_GLOBALSINDEX); - L.getfield("_processQueueForViewportId", -1); - L.copy(-2); - L.push(lua_Number(viewport->ID)); - try { - L.pcall(2); - bool gotGLerror = false; - for (const auto& error : m_glErrors) { - m_luaConsole.addError(error); - if (g_system->getArgs().isLuaStdoutEnabled()) { - fputs(error.c_str(), stderr); - fputc('\n', stderr); + auto nvgSubContext = m_nvgSubContextes.find(viewport->ID); + if (nvgSubContext != m_nvgSubContextes.end()) { + glfwGetWindowSize(window, &winWidth, &winHeight); + glfwGetFramebufferSize(window, &fbWidth, &fbHeight); + pxRatio = (float)fbWidth / (float)winWidth; + nvgSwitchSubContextGL(vg, nvgSubContext->second); + nvgBeginFrame(vg, winWidth, winHeight, pxRatio); + L.getfieldtable("nvg", LUA_GLOBALSINDEX); + L.getfield("_processQueueForViewportId", -1); + L.copy(-2); + L.push(lua_Number(viewport->ID)); + try { + L.pcall(2); + bool gotGLerror = false; + for (const auto& error : m_glErrors) { + m_luaConsole.addError(error); + if (g_system->getArgs().isLuaStdoutEnabled()) { + fputs(error.c_str(), stderr); + fputc('\n', stderr); + } + gotGLerror = true; } - gotGLerror = true; + m_glErrors.clear(); + if (gotGLerror) throw("OpenGL error while running NanoVG queue"); + } catch (...) { } - m_glErrors.clear(); - if (gotGLerror) throw("OpenGL error while running NanoVG queue"); - } catch (...) { + nvgEndFrame(vg); + while (L.gettop()) L.pop(); } - nvgEndFrame(vg); - while (L.gettop()) L.pop(); } if (platform_io.Platform_SwapBuffers) platform_io.Platform_SwapBuffers(viewport, nullptr); if (platform_io.Renderer_SwapBuffers) platform_io.Renderer_SwapBuffers(viewport, nullptr); } glfwMakeContextCurrent(m_window); } + glfwSwapBuffers(m_window); L.getfieldtable("nvg", LUA_GLOBALSINDEX); L.push("_gui"); @@ -2271,6 +2278,84 @@ bool PCSX::GUI::about() { ImGui::EndChild(); ImGui::EndTabItem(); } + if (ImGui::BeginTabItem(_("FFmpeg information"))) { + ImGui::Text(_("Version: %s"), av_version_info()); + ImGui::Text(_("License: %s"), avutil_license()); + ImGui::TextWrapped(_("Configuration: %s"), avutil_configuration()); + ImGui::Separator(); + + ImGui::TextUnformatted(_("List of supported formats:")); + const AVInputFormat* format = nullptr; + void* opaque = nullptr; + std::vector formats; + unsigned nb_formats = 0; + while ((format = av_demuxer_iterate(&opaque))) nb_formats++; + formats.reserve(nb_formats); + opaque = nullptr; + while ((format = av_demuxer_iterate(&opaque))) formats.push_back(format); + std::sort(formats.begin(), formats.end(), + [](auto& a, auto& b) { return strcmp(a->name, b->name) < 0; }); + useMonoFont(); + for (auto& format : formats) { + ImGui::Text(" %-25s %s", format->name, format->long_name); + } + ImGui::PopFont(); + ImGui::Separator(); + + ImGui::TextUnformatted(_("List of supported codecs: (D: Decoder, E: Encoder, L: Lossy, S: Lossless)")); + const AVCodecDescriptor* codec = nullptr; + std::vector codecs; + unsigned nb_codecs = 0; + while ((codec = avcodec_descriptor_next(codec))) nb_codecs++; + codecs.reserve(nb_codecs); + codec = nullptr; + while ((codec = avcodec_descriptor_next(codec))) codecs.push_back(codec); + std::sort(codecs.begin(), codecs.end(), [](auto& a, auto& b) { + if (a->type == b->type) { + return strcmp(a->name, b->name) < 0; + } + return a->type < b->type; + }); + + auto getMediaType = [](AVMediaType type) -> const char* { + switch (type) { + case AVMEDIA_TYPE_VIDEO: + return "Video"; + case AVMEDIA_TYPE_AUDIO: + return "Audio"; + case AVMEDIA_TYPE_DATA: + return "Data"; + case AVMEDIA_TYPE_SUBTITLE: + return "Subtitle"; + case AVMEDIA_TYPE_ATTACHMENT: + return "Attachment"; + default: + return "Unknown"; + } + }; + + auto previousType = AVMEDIA_TYPE_UNKNOWN; + + useMonoFont(); + for (auto& codec : codecs) { + auto type = codec->type; + if (type != previousType) { + ImGui::Separator(); + useMainFont(); + ImGui::Text(_("%s codecs"), getMediaType(type)); + ImGui::PopFont(); + previousType = type; + } + std::string_view name = codec->name; + if (StringsHelpers::endsWith(name, "_deprecated")) continue; + ImGui::Text(" %c%c%c%c %-20s %s", avcodec_find_decoder(codec->id) ? 'D' : '.', + avcodec_find_encoder(codec->id) ? 'E' : '.', + (codec->props & AV_CODEC_PROP_LOSSY) ? 'L' : '.', + (codec->props & AV_CODEC_PROP_LOSSLESS) ? 'S' : '.', codec->name, codec->long_name); + } + ImGui::PopFont(); + ImGui::EndTabItem(); + } ImGui::EndTabBar(); } } diff --git a/src/gui/luaimguiextra.cc b/src/gui/luaimguiextra.cc index 56ced88e5..c55106e9d 100644 --- a/src/gui/luaimguiextra.cc +++ b/src/gui/luaimguiextra.cc @@ -20,6 +20,7 @@ #include "gui/luaimguiextra.h" #include "imgui/imgui.h" +#include "imgui_stdlib.h" #include "lua/luawrapper.h" namespace { @@ -69,4 +70,77 @@ void PCSX::LuaFFI::open_imguiextra(Lua L) { #include "gui/imguiextraffi.lua" ); L.load(imguiextra, "internal:gui/imguiextraffi.lua"); + + L.getfieldtable("imgui", LUA_GLOBALSINDEX); + L.getfieldtable("extra"); + L.declareFunc( + "InputText", + [](lua_State* L_) -> int { + Lua L(L_); + int n = L.gettop(); + if (n < 2) { + return L.error("InputText: not enough arguments"); + } + if (n > 3) { + return L.error("InputText: too many arguments"); + } + if (!L.isstring(1)) { + return L.error("InputText: argument 1 must be a string"); + } + if (!L.isstring(2)) { + return L.error("InputText: argument 2 must be a string"); + } + std::string label = L.tostring(1); + std::string str = L.tostring(2); + ImGuiInputTextFlags flags = 0; + if (n == 3) { + if (!L.isnumber(3)) { + return L.error("InputText: argument 3 must be a number"); + } + flags = L.tonumber(3); + } + bool ret = ImGui::InputText(label.c_str(), &str, flags); + L.push(ret); + L.push(str); + return 2; + }, + -1); + L.declareFunc( + "InputTextWithHint", + [](lua_State* L_) -> int { + Lua L(L_); + int n = L.gettop(); + if (n < 3) { + return L.error("InputTextWithHint: not enough arguments"); + } + if (n > 4) { + return L.error("InputTextWithHint: too many arguments"); + } + if (!L.isstring(1)) { + return L.error("InputTextWithHint: argument 1 must be a string"); + } + if (!L.isstring(2)) { + return L.error("InputTextWithHint: argument 2 must be a string"); + } + if (!L.isstring(3)) { + return L.error("InputTextWithHint: argument 3 must be a string"); + } + std::string label = L.tostring(1); + std::string hint = L.tostring(2); + std::string str = L.tostring(3); + ImGuiInputTextFlags flags = 0; + if (n == 4) { + if (!L.isnumber(4)) { + return L.error("InputTextWithHint: argument 4 must be a number"); + } + flags = L.tonumber(4); + } + bool ret = ImGui::InputTextWithHint(label.c_str(), hint.c_str(), &str, flags); + L.push(ret); + L.push(str); + return 2; + }, + -1); + L.pop(2); + assert(L.gettop() == 0); } diff --git a/src/gui/resources-unix.cc b/src/gui/resources-unix.cc index d82d6fc01..528bfcae8 100644 --- a/src/gui/resources-unix.cc +++ b/src/gui/resources-unix.cc @@ -49,8 +49,7 @@ void PCSX::Resources::loadIcon(std::function pro info[i].offset = ico->read(); } for (unsigned i = 0; i < count; i++) { - ico->rSeek(info[i].offset, SEEK_SET); - auto slice = ico->read(info[i].size); + auto slice = ico->readAt(info[i].size, info[i].offset); process(reinterpret_cast(slice.data()), slice.size()); } } diff --git a/src/gui/widgets/isobrowser.cc b/src/gui/widgets/isobrowser.cc index 898865e70..2ba372e9c 100644 --- a/src/gui/widgets/isobrowser.cc +++ b/src/gui/widgets/isobrowser.cc @@ -66,7 +66,7 @@ void PCSX::Widgets::IsoBrowser::draw(CDRom* cdrom, const char* title) { if (ImGui::BeginMenu(_("File"))) { showOpenIsoFileDialog = ImGui::MenuItem(_("Open Disk Image")); if (ImGui::MenuItem(_("Close Disk Image"))) { - g_emulator->m_cdrom->setIso(new CDRIso()); + g_emulator->m_cdrom->setIso(new CDRIso(new FailedFile)); g_emulator->m_cdrom->parseIso(); } ImGui::EndMenu(); diff --git a/src/gui/widgets/shader-editor.cc b/src/gui/widgets/shader-editor.cc index ab5b1e15c..349822711 100644 --- a/src/gui/widgets/shader-editor.cc +++ b/src/gui/widgets/shader-editor.cc @@ -830,7 +830,7 @@ void PCSX::Widgets::ShaderEditor::imguiCB(const ImDrawList *parentList, const Im } void PCSX::Widgets::ShaderEditor::render(GUI *gui, GLuint textureID, const ImVec2 &srcLoc, const ImVec2 &srcSize, - const ImVec2 &dstSize) { + const ImVec2 &dstSize, std::initializer_list extraArgs) { if (m_shaderProgram == 0) { compile(gui); } @@ -912,9 +912,12 @@ void PCSX::Widgets::ShaderEditor::render(GUI *gui, GLuint textureID, const ImVec L.push(srcSize.y); L.push(dstSize.x); L.push(dstSize.y); + for (auto arg : extraArgs) { + L.push(arg); + } GUI::ScopedOnlyLog scopedOnlyLog(gui); try { - L.pcall(8); + L.pcall(8 + extraArgs.size()); bool gotGLerror = false; auto errors = gui->getGLerrors(); for (const auto &error : errors) { diff --git a/src/gui/widgets/shader-editor.h b/src/gui/widgets/shader-editor.h index 67045249e..c37997e0e 100644 --- a/src/gui/widgets/shader-editor.h +++ b/src/gui/widgets/shader-editor.h @@ -19,6 +19,7 @@ #pragma once +#include #include #include #include @@ -65,7 +66,8 @@ class ShaderEditor { bool draw(GUI*, const char* title); void renderWithImgui(GUI* gui, ImTextureID textureID, const ImVec2& srcSize, const ImVec2& dstSize); - void render(GUI*, GLuint textureID, const ImVec2& srcLoc, const ImVec2& srcSize, const ImVec2& dstSize); + void render(GUI*, GLuint textureID, const ImVec2& srcLoc, const ImVec2& srcSize, const ImVec2& dstSize, + std::initializer_list extraArgs = {}); void setConfigure(bool configure = true); void configure(GUI*); diff --git a/src/lua/extra.cc b/src/lua/extra.cc index 5c4a1bb35..f9bd0e69f 100644 --- a/src/lua/extra.cc +++ b/src/lua/extra.cc @@ -38,9 +38,12 @@ void PCSX::LuaFFI::open_extra(Lua L) { luaopen_pb_unsafe(L.getState()); L.setfield("pb.unsafe"); - static int lualoader = 6; + static int lualoader = 7; static const char* pprint = ( #include "pprint.lua/pprint.lua" + ); + static const char* pprint_internals = ( +#include "pprint.lua/pprint-internals.lua" ); static const char* reflectFFI = ( #include "ffi-reflect/reflect.lua" @@ -58,6 +61,7 @@ void PCSX::LuaFFI::open_extra(Lua L) { #include "lua-protobuf/protoc.lua" ); L.load(pprint, "internal:pprinter.lua/pprint.lua"); + L.load(pprint_internals, "internal:pprinter.lua/pprint-internals.lua"); L.load(reflectFFI, "internal:ffi-reflect/reflect.lua"); L.load(protobufLexer, "internal:lua-protobuf/lexer.lua"); diff --git a/src/lua/fileffi-cdef.lua b/src/lua/fileffi-cdef.lua index 81b27a2f7..810c49162 100644 --- a/src/lua/fileffi-cdef.lua +++ b/src/lua/fileffi-cdef.lua @@ -97,6 +97,12 @@ LuaFile* mem4g(); uint32_t mem4gLowestAddress(LuaFile*); uint32_t mem4gHighestAddress(LuaFile*); uint32_t mem4gActualSize(LuaFile*); + +enum FFmpegAudioFileChannels { Stereo, Mono }; +enum FFmpegAudioFileEndianness { Little, Big }; +enum FFmpegAudioFileSampleFormat { U8, S16, S32, F32, D64 }; +LuaFile* ffmpegAudioFile(LuaFile* file, enum FFmpegAudioFileChannels, enum FFmpegAudioFileEndianness, enum FFmpegAudioFileSampleFormat, unsigned frequency); + ]] -- )EOF" diff --git a/src/lua/fileffi.lua b/src/lua/fileffi.lua index 11eaca0f1..0fc32cac6 100644 --- a/src/lua/fileffi.lua +++ b/src/lua/fileffi.lua @@ -94,7 +94,7 @@ end local function readAt(self, ptr, size, pos) if type(ptr) == 'number' and type(size) == 'number' and pos == nil then - ptr = size + pos = size size = ptr local buf = Support.NewLuaBuffer(size) size = C.readFileAtBuffer(self._wrapper, buf, pos) @@ -340,6 +340,12 @@ local function mem4g() return ret end +local function ffmpegAudioFile(file, options) + if type(options) ~= 'table' then options = {} end + local channels, endianness, sampleFormat, frequency = options.channels, options.endianness, options.sampleFormat, options.frequency + return createFileWrapper(C.ffmpegAudioFile(file._wrapper, channels or 'Stereo', endianness or 'Little', sampleFormat or 'S16', frequency or 44100)) +end + if (type(Support) ~= 'table') then Support = {} end Support.NewLuaBuffer = function(size) @@ -357,6 +363,7 @@ Support.File = { uvFifo = uvFifo, mem4g = mem4g, failedFile = function() return createFileWrapper(C.failedFile()) end, + ffmpegAudioFile = ffmpegAudioFile, _createFileWrapper = createFileWrapper, _createSliceWrapper = createSliceWrapper, } diff --git a/src/lua/luafile.cc b/src/lua/luafile.cc index 4442a97e1..424e0af26 100644 --- a/src/lua/luafile.cc +++ b/src/lua/luafile.cc @@ -22,6 +22,7 @@ #include "core/system.h" #include "lua-protobuf/pb.h" #include "lua/luawrapper.h" +#include "support/ffmpeg-audio-file.h" #include "support/mem4g.h" #include "support/uvfile.h" #include "support/zfile.h" @@ -260,6 +261,12 @@ uint32_t mem4gLowestAddress(LuaFile* file) { return file->file.asA( uint32_t mem4gHighestAddress(LuaFile* file) { return file->file.asA()->highestAddress(); } uint32_t mem4gActualSize(LuaFile* file) { return file->file.asA()->actualSize(); } +LuaFile* ffmpegAudioFile(LuaFile* file, PCSX::FFmpegAudioFile::Channels channels, + PCSX::FFmpegAudioFile::Endianness endianness, PCSX::FFmpegAudioFile::SampleFormat sampleFormat, + unsigned frequency) { + return new LuaFile(new PCSX::FFmpegAudioFile(file->file, channels, endianness, sampleFormat, frequency)); +} + } // namespace template @@ -334,6 +341,8 @@ static void registerAllSymbols(PCSX::Lua L) { REGISTER(L, mem4gHighestAddress); REGISTER(L, mem4gActualSize); + REGISTER(L, ffmpegAudioFile); + L.settable(); L.pop(); } diff --git a/src/main/main.cc b/src/main/main.cc index 62d63d379..5f06f6f6b 100644 --- a/src/main/main.cc +++ b/src/main/main.cc @@ -359,9 +359,9 @@ int pcsxMain(int argc, char **argv) { emulator->m_spu->close(); emulator->m_cdrom->clearIso(); - emulator->m_cpu->psxShutdown(); emulator->m_spu->shutdown(); emulator->m_gpu->shutdown(); + emulator->shutdown(); s_ui->close(); delete s_ui; diff --git a/src/main/main.h b/src/main/main.h index 6d984dc36..8a4f662f3 100644 --- a/src/main/main.h +++ b/src/main/main.h @@ -39,7 +39,7 @@ class MainInvoker { for (char** ptr = m_args; *ptr; ptr++) { free(*ptr); } - delete m_args; + delete[] m_args; } int invoke() { fprintf(stderr, "Starting PCSX-Redux test with arguments:\n"); diff --git a/src/mips/common/psxlibc/string.h b/src/mips/common/psxlibc/string.h index 62efcb727..03a385760 100644 --- a/src/mips/common/psxlibc/string.h +++ b/src/mips/common/psxlibc/string.h @@ -32,7 +32,7 @@ SOFTWARE. static __attribute__((always_inline)) uint8_t* safeMemZero(uint8_t* ptr, int size) { if (!ptr || size <= 0) return NULL; uint8_t* orig = ptr; - for (; size >= 0; ptr++) { + for (; size > 0; ptr++) { size--; *ptr = 0; } diff --git a/src/mips/common/syscalls/syscalls.h b/src/mips/common/syscalls/syscalls.h index 27f81371f..9bfb398bb 100644 --- a/src/mips/common/syscalls/syscalls.h +++ b/src/mips/common/syscalls/syscalls.h @@ -40,20 +40,18 @@ static __attribute__((always_inline)) int enterCriticalSection() { register int n asm("a0") = 1; register int r asm("v0"); __asm__ volatile("syscall\n" - : "=r"(n), "=r"(r) + : "=r"(r) : "r"(n) - : "at", "v1", "a1", "a2", "a3", "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9", - "memory"); + : "memory"); return r; } static __attribute__((always_inline)) void leaveCriticalSection() { register int n asm("a0") = 2; __asm__ volatile("syscall\n" - : "=r"(n) + : : "r"(n) - : "at", "v0", "v1", "a1", "a2", "a3", "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9", - "memory"); + : "memory"); } static __attribute__((always_inline)) int changeThreadSubFunction(uint32_t address) { @@ -61,9 +59,9 @@ static __attribute__((always_inline)) int changeThreadSubFunction(uint32_t addre register int tcb asm("a1") = address; register int r asm("v0"); __asm__ volatile("syscall\n" - : "=r"(r), "=r"(n), "=r"(tcb) + : "=r"(r) : "r"(n), "r"(tcb) - : "at", "v1", "a2", "a3", "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "memory"); + : "memory"); return r; } diff --git a/src/mips/openbios/Makefile b/src/mips/openbios/Makefile index 7bb0a669b..d00109460 100644 --- a/src/mips/openbios/Makefile +++ b/src/mips/openbios/Makefile @@ -48,6 +48,7 @@ patches/hash.c \ patches/patches.c \ \ patches/clear_card_1.c \ +patches/custom_handler_1.c \ patches/initgun_1.c \ patches/patch_card_info_1.c \ patches/patch_card_1.c \ diff --git a/src/mips/openbios/boot/boot.s b/src/mips/openbios/boot/boot.s index b71cde970..338aba09b 100644 --- a/src/mips/openbios/boot/boot.s +++ b/src/mips/openbios/boot/boot.s @@ -106,6 +106,14 @@ _boot: li $t0, 0x80777 sw $t0, SBUS_DEV8_CTRL + /* Extra from OpenBIOS, not in the original BIOS: + in case we booted from a cart, we move the + flushCache pointer at 0x310 to our own storage, + so we can use it later. */ + lw $t0, 0x310($0) + lui $t1, %hi(__flush_cache_real_bios_ptr) + sw $t0, %lo(__flush_cache_real_bios_ptr)($t1) + /* clearing out all registers */ .set push .set noat @@ -162,6 +170,14 @@ _boot: mtc0 $0, $13 nop + /* Extra from OpenBIOS, not in the original BIOS: + Enable cop2, as some games may rely on it being + enabled as a side effect of the shell running, + and our replacement shell does not enable it. */ + lui $t0, 0x4000 + mtc0 $t0, $12 + nop + /* Now we are ready for a typical crt0. The original bios does not do this, most likely for speed reasons. It would be more efficient to @@ -223,47 +239,42 @@ _cartBoot: lui $t0, 0b1100101010000000 li $t1, 0x0314 li $t2, 0xffff - mtc0 $t0, $7 + mtc0 $0, $7 mtc0 $t1, $5 mtc0 $t2, $9 + mtc0 $t0, $7 lui $t9, %hi(cartBootCop0Hook) lw $t0, (%lo(cartBootCop0Hook)+0x00)($t9) lw $t1, (%lo(cartBootCop0Hook)+0x04)($t9) lw $t2, (%lo(cartBootCop0Hook)+0x08)($t9) lw $t3, (%lo(cartBootCop0Hook)+0x0c)($t9) - lw $t4, (%lo(cartBootCop0Hook)+0x10)($t9) - lw $t5, (%lo(cartBootCop0Hook)+0x14)($t9) sw $t0, 0x40($0) sw $t1, 0x44($0) sw $t2, 0x48($0) - sw $t3, 0x4c($0) - sw $t4, 0x50($0) /* ironically, what we just did technically requires calling flushCache, but since our whole point here is to grab its pointer, we obviously cannot, and we're going to rely on determinicity instead, which means some emulators may break on this */ jr $ra - sw $t5, 0x54($0) + sw $t3, 0x4c($0) cartBootCop0Hook: - /* and finally, store the flushCache pointer at - address 0x5c, where hopefully nobody will write to, - so we can jump back to it from our thunk, before - jumping to _reset, in order to proceed with the - normal boot process - don't forget to disable the - breakpoint otherwise we'll be in trouble */ - lw $t0, 0x310($0) - la $t1, _reset - sw $t0, 0x5c($0) - j $t1 + /* and finally, after the above breakpoint triggers, + we jump to _reset in order to boot our own + version of the bios and kernel, but don't + forget to disable the breakpoint, otherwise + we'll be in trouble */ + la $t0, _reset + j $t0 mtc0 $0, $7 .global flushCacheFromRealBios .type flushCacheFromRealBios, @function flushCacheFromRealBios: - lw $t0, 0x5c($0) + lui $t0, %hi(__flush_cache_real_bios_ptr) + lw $t0, %lo(__flush_cache_real_bios_ptr)($t0) nop jr $t0 nop diff --git a/src/mips/openbios/kernel/vectors.s b/src/mips/openbios/kernel/vectors.s index 339552b4d..703675832 100644 --- a/src/mips/openbios/kernel/vectors.s +++ b/src/mips/openbios/kernel/vectors.s @@ -88,7 +88,8 @@ noCOP2adjustmentNeeded: /* These 4 blocs of 4 nops are each for an early exception handler slot. Only registers at, v0, v1, and ra are saved at this point. The first slot is used by the memory card driver. The second is used by the - lightgun driver. These are sometimes cleared out by patches. */ + lightgun driver, and a custom handler from the game Point Blank. + These are sometimes cleared out by patches. */ exceptionHandlerPatchSlot1: nop @@ -308,11 +309,59 @@ exceptionVector: This results in the address 0x80 being wrongly dereferenced by CdControlF to read its parameters, and the retail bios will have enough zeroes here to make it work properly, tho it will be at - 1x speed instead of the intended 2x. We pad with a nop to ensure - a behavior that is somewhat similar to the retail bios. */ - - nop - li $k0, %lo(exceptionHandler) + 1x speed instead of the intended 2x. + + Now, we cannot just use a nop here, even though it would be the + simplest way to ensure a valid CdControlF call, because then, + Batman - Gotham City Racer NTSC (SLUS-01141) replaces this + code by reading the exception handler until a nop is found: + + copyHandler XREF[1]: FUN_80016a34:80016a68 (c) + 8002668c 80 00 04 24 li src ,0x80 + 80026690 02 80 02 3c lui v0,0x8002 + 80026694 a8 67 42 24 addiu dst ,dst ,0x67a8 + LAB_80026698 XREF[1]: 800266a4 (j) + 80026698 00 00 88 8c lw opcode ,0x0 (src )=>DAT_00000080 + 8002669c 04 00 84 20 addi src ,src ,0x4 + 800266a0 00 00 48 ac sw opcode ,0x0 (dst )=>DAT_800267a8 + 800266a4 fc ff 00 15 bne opcode ,zero ,LAB_80026698 + 800266a8 04 00 42 20 _addi dst ,dst ,0x4 + + So if we want to keep the game working, we need to have a valid + no-op instruction that is not a nop. + + Using lui $0, 0x80 will not only accomplish these goals, but also + ensure that a CdControlF(0x80) actually sets the drive at 2x speed. + + However, Need for Speed - High Stakes will run this code to detect + the presence of a debugger: + + detectDevBIOS XREF[1]: FUN_800f4390:800f43bc (c) + 8010769c 86 00 04 94 lhu a0,0x86 (zero ) + 801076a0 5a 37 02 24 li v0,0x375a + 801076a4 05 00 82 10 beq a0,v0,LAB_801076bc + 801076a8 5a 27 03 24 _li v1,0x275a + 801076ac 04 00 83 10 beq a0,v1,LAB_801076c0 + 801076b0 21 10 00 00 _clear ret + 801076b4 08 00 e0 03 jr ra + 801076b8 ff ff 02 24 _li ret ,-0x1 + LAB_801076bc XREF[1]: 801076a4 (j) + 801076bc 01 00 02 24 li ret ,0x1 + LAB_801076c0 XREF[1]: 801076ac (j) + 801076c0 08 00 e0 03 jr ra + 801076c4 00 00 00 00 _nop + + In other words, it will check if the 16-bits value at 0x86 is 0x275a + for a retail bios, or 0x375a for a debugger. If it is neither, it + will return the code -1, for an error. 0x375a corresponds to + `ori $k0, $k0, xxx`, and 0x275a corresponds to `addiu $k0, $k0, xxx`. + + At this point, our only safe option is to use a `la`, which will + expand into lui / addiu, with the low portion of the lui being + 0x0000, to satisfy all of our constraints. + */ + + la $k0, exceptionHandler jr $k0 nop diff --git a/src/mips/openbios/main/main.c b/src/mips/openbios/main/main.c index 0cd71ab57..ddcc75426 100644 --- a/src/mips/openbios/main/main.c +++ b/src/mips/openbios/main/main.c @@ -126,7 +126,7 @@ static char s_binaryPath[128]; // Specifically, the BOOT line parser will try to parse a command // line argument. There are multiple ways this can happen on a // retail bios, but this parser will chose to separate it using -// the tabulation character ('\t', or character 9). +// the tabulation character ('\t', or character 9) or a space. // Last but not least, the retail bios will screw things up // fairly badly if the file isn't terminated using CRLFs. static void findWordItem(const char *systemCnf, uint32_t *item, const char *name) { @@ -161,7 +161,7 @@ static void findWordItem(const char *systemCnf, uint32_t *item, const char *name value |= c - 'a' + 10; } } else { - if ((c == 0) || (c == '\n') || (c == '\r')) { + if ((c == 0) || isspace(c)) { *item = value; psxprintf("%s\t%08x\n", name, value); } @@ -194,7 +194,7 @@ static void findStringItem(const char *systemCnf, char *const binaryPath, char * c = *systemCnf++; if (isspace(c) && !started) continue; started = 1; - if ((parseArg = (c == '\t'))) break; + if ((parseArg = (c == '\t') || (c == ' '))) break; if ((c == '\r') || (c == '\n') || (c == 0)) break; *binPtr++ = c; } @@ -268,6 +268,9 @@ void gameMainThunk(struct psxExeHeader *binaryInfo, int argc, char **argv) { if (cdromReset() < 0) syscall_exception(0x44, 0x38b); } enterCriticalSection(); + // Fixing SaGa Frontier (USA) when in fastboot mode, as it relies + // on the side effect of the shell running to enable the display. + GPU_STATUS = 0x03000000; exec(binaryInfo, argc, argv); } diff --git a/src/mips/openbios/patches/Makefile b/src/mips/openbios/patches/Makefile index f82291fc9..1d6eb8bfc 100644 --- a/src/mips/openbios/patches/Makefile +++ b/src/mips/openbios/patches/Makefile @@ -1,5 +1,6 @@ PATCHES = \ clear_card_1.c \ +custom_handler_1.c \ initgun_1.c \ patch_card_info_1.c \ patch_card_1.c \ diff --git a/src/mips/openbios/patches/custom_handler_1.c b/src/mips/openbios/patches/custom_handler_1.c new file mode 100644 index 000000000..27554b4b2 --- /dev/null +++ b/src/mips/openbios/patches/custom_handler_1.c @@ -0,0 +1,151 @@ +/* + +MIT License + +Copyright (c) 2023 PCSX-Redux authors + +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 "openbios/patches/patches.h" +#include "openbios/sio0/pad.h" + +// clang-format off + +/* Found in Point Blank USA: + + ************************************************************* + * FUNCTION + ************************************************************* + undefined installCustomHandler () + assume gp = 0x8009c180 + undefined v0:1 XREF[2]: 800606d4 (W) , + 800606f0 (W) + dword * v0:4 c0table XREF[2]: 800606d4 (W) , + 800606f0 (W) + dword * v0:4 exceptionHandlerPtr XREF[1]: 800606f0 (W) + installCustomHandler XREF[1]: FUN_800601bc:800602ac (c) + 800606b4 0a 80 01 3c lui at,0x800a + 800606b8 ac c3 3f ac sw ra,-0x3c54 (at)=>DAT_8009c3ac + 800606bc 0a 80 01 3c lui at,0x800a + 800606c0 a8 c3 24 ac sw a0,-0x3c58 (at)=>DAT_8009c3a8 + 800606c4 0a 80 01 3c lui at,0x800a + 800606c8 49 7c 01 0c jal FUN_8005f124 undefined FUN_8005f124() + 800606cc b0 c3 25 ac _sw a1,-0x3c50 (at)=>DAT_8009c3b0 + 800606d0 b0 00 0a 24 li t2,0xb0 + 800606d4 09 f8 40 01 jalr t2=>SUB_000000b0 + 800606d8 56 00 09 24 _li t1,0x56 + 800606dc 06 80 0a 3c lui t2,0x8006 + 800606e0 06 80 09 3c lui t1,0x8006 + 800606e4 18 00 42 8c lw c0table ,0x18 (c0table ) + 800606e8 3c 08 4a 25 addiu t2,t2,0x83c + 800606ec 4c 08 29 25 addiu t1,t1,0x84c + LAB_800606f0 XREF[1]: 800606fc (j) + 800606f0 00 00 43 8d lw v1,0x0 (t2)=>LAB_8006083c + 800606f4 04 00 4a 25 addiu t2,t2,0x4 + 800606f8 04 00 42 24 addiu exceptionHandlerPtr ,exceptionHandlerPtr ,0x4 + 800606fc fc ff 49 15 bne t2,t1,LAB_800606f0 + 80060700 7c 00 43 ac _sw v1,0x7c (exceptionHandlerPtr ) + 80060704 06 80 02 3c lui exceptionHandlerPtr ,0x8006 + 80060708 dc 09 42 24 addiu exceptionHandlerPtr ,exceptionHandlerPtr ,0x9dc + 8006070c 00 00 4a 8c lw t2,0x0 (exceptionHandlerPtr )=>DAT_800609dc + 80060710 00 00 00 00 nop + 80060714 12 00 40 15 bne t2,zero ,LAB_80060760 + 80060718 00 00 00 00 _nop + 8006071c 80 00 0a 24 li t2,0x80 + 80060720 90 00 09 24 li t1,0x90 + LAB_80060724 XREF[1]: 80060730 (j) + 80060724 00 00 43 8d lw v1,0x0 (t2)=>DAT_00000080 + 80060728 04 00 4a 25 addiu t2,t2,0x4 + 8006072c 04 00 42 24 addiu exceptionHandlerPtr ,exceptionHandlerPtr ,0x4 + 80060730 fc ff 49 15 bne t2,t1,LAB_80060724 + 80060734 fc ff 43 ac _sw v1,-0x4 (exceptionHandlerPtr )=>DAT_800609dc + 80060738 06 80 0a 3c lui t2,0x8006 + 8006073c 06 80 09 3c lui t1,0x8006 + 80060740 80 00 02 24 li exceptionHandlerPtr ,0x80 + 80060744 a0 09 4a 25 addiu t2,t2,0x9a0 + 80060748 b0 09 29 25 addiu t1,t1,0x9b0 + LAB_8006074c XREF[1]: 80060758 (j) + 8006074c 00 00 43 8d lw v1,0x0 (t2)=>LAB_800609a0 + 80060750 04 00 4a 25 addiu t2,t2,0x4 + 80060754 04 00 42 24 addiu exceptionHandlerPtr ,exceptionHandlerPtr ,0x4 + 80060758 fc ff 49 15 bne t2,t1,LAB_8006074c + 8006075c fc ff 43 ac _sw v1,-0x4 (exceptionHandlerPtr )=>DAT_00000080 + LAB_80060760 XREF[1]: 80060714 (j) + 80060760 11 7c 01 0c jal FlushCache void FlushCache(void) + 80060764 00 00 00 00 _nop + 80060768 0a 80 1f 3c lui ra,0x800a + 8006076c ac c3 ff 8f lw ra,-0x3c54 (ra)=>DAT_8009c3ac + 80060770 00 00 00 00 nop + 80060774 08 00 e0 03 jr ra + 80060778 00 00 00 00 _nop + + This patch is very much custom, and doesn't seem to be coming from the Psy-Q SDK + like the other patches. It's injecting two custom exception handlers into the kernel, + once at the beginning of the exception handler itself, and one straight into + 0x80000080, which is probably the only time this has ever been done. + + Luckily, we can simply "do nothing" when encountering this patch, as the exception + handler will play nice with this patch. + */ + +// clang-format on + +#ifndef GENERATE_HASHES + +enum patch_behavior custom_handler_1_execute(uint32_t* ra) { + return PATCH_PASSTHROUGH; +} + +#else + +#include "openbios/patches/hash.h" + +static const uint8_t masks[] = { + 1, 1, 0, 1, // 00 + 1, 0, 0, 0, // 10 + 0, 0, 1, 1, // 20 + 0, 0, 0, 0, // 30 +}; + +static const uint8_t bytes[] = { + 0x06, 0x80, 0x0a, 0x3c, 0x06, 0x80, 0x09, 0x3c, 0x18, 0x00, 0x42, 0x8c, 0x3c, 0x08, 0x4a, 0x25, // 00 + 0x4c, 0x08, 0x29, 0x25, 0x00, 0x00, 0x43, 0x8d, 0x04, 0x00, 0x4a, 0x25, 0x04, 0x00, 0x42, 0x24, // 10 + 0xfc, 0xff, 0x49, 0x15, 0x7c, 0x00, 0x43, 0xac, 0x06, 0x80, 0x02, 0x3c, 0xdc, 0x09, 0x42, 0x24, // 20 + 0x00, 0x00, 0x4a, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x40, 0x15, 0x00, 0x00, 0x00, 0x00, // 30 +}; + +uint32_t generate_hash_custom_handler_1(uint32_t mask, unsigned len) { + return patch_hash((const uint32_t *)bytes, (uint8_t *)&mask, len); +} + +uint32_t generate_mask_custom_handler_1() { + uint32_t mask = 0; + + for (unsigned i = 0; i < 16; i++) { + mask <<= 2; + mask |= masks[15 - i]; + } + + return mask; +} +#endif diff --git a/src/mips/openbios/patches/generate.c b/src/mips/openbios/patches/generate.c index d3ab8c1f4..3febeba25 100644 --- a/src/mips/openbios/patches/generate.c +++ b/src/mips/openbios/patches/generate.c @@ -54,6 +54,8 @@ uint32_t generate_mask_send_pad_2(); /* C0 */ uint32_t generate_hash_clear_card_1(uint32_t mask, unsigned len); uint32_t generate_mask_clear_card_1(); +uint32_t generate_hash_custom_handler_1(uint32_t mask, unsigned len); +uint32_t generate_mask_custom_handler_1(); uint32_t generate_hash_initgun_1(uint32_t mask, unsigned len); uint32_t generate_mask_initgun_1(); uint32_t generate_hash_patch_card_1(uint32_t mask, unsigned len); @@ -146,6 +148,12 @@ static const struct patch c0[] = { .name = "_clear_card#1", .execute = "clear_card_1_execute", }, + { + .hash = generate_hash_custom_handler_1, + .mask = generate_mask_custom_handler_1, + .name = "custom_handler#1", + .execute = "custom_handler_1_execute", + }, { .hash = generate_hash_initgun_1, .mask = generate_mask_initgun_1, diff --git a/src/mips/openbios/patches/patches.c b/src/mips/openbios/patches/patches.c index 49fc04fa6..ac2f36822 100644 --- a/src/mips/openbios/patches/patches.c +++ b/src/mips/openbios/patches/patches.c @@ -55,6 +55,7 @@ enum patch_behavior remove_ChgclrPAD_2_execute(uint32_t* ra); enum patch_behavior send_pad_1_execute(uint32_t* ra); enum patch_behavior send_pad_2_execute(uint32_t* ra); enum patch_behavior clear_card_1_execute(uint32_t* ra); +enum patch_behavior custom_handler_1_execute(uint32_t* ra); enum patch_behavior initgun_1_execute(uint32_t* ra); enum patch_behavior patch_card_1_execute(uint32_t* ra); enum patch_behavior patch_card_2_execute(uint32_t* ra); @@ -125,6 +126,11 @@ static const struct patch C0patches[] = { .execute = clear_card_1_execute, .name = "_clear_card#1", }, + { + .hash = 0xf80aeee3, + .execute = custom_handler_1_execute, + .name = "custom_handler#1", + }, { .hash = 0x5753f599, .execute = initgun_1_execute, diff --git a/src/mips/openbios/psx-bios-as-cart.ld b/src/mips/openbios/psx-bios-as-cart.ld index 40fa9031a..c621bc42f 100644 --- a/src/mips/openbios/psx-bios-as-cart.ld +++ b/src/mips/openbios/psx-bios-as-cart.ld @@ -198,6 +198,12 @@ SECTIONS { __bss_end = .; } > kseg0 + .flushCacheHack (NOLOAD) : { + . = ALIGN(4); + __flush_cache_real_bios_ptr = .; + LONG(0); + } > kseg0 + __heap_start = .; __end = .; diff --git a/src/mips/openbios/psx-bios.ld b/src/mips/openbios/psx-bios.ld index d63138824..0884e9cdc 100644 --- a/src/mips/openbios/psx-bios.ld +++ b/src/mips/openbios/psx-bios.ld @@ -181,6 +181,12 @@ SECTIONS { __bss_end = .; } > kseg0 + .flushCacheHack (NOLOAD) : { + . = ALIGN(4); + __flush_cache_real_bios_ptr = .; + LONG(0); + } > kseg0 + __heap_start = .; __end = .; @@ -192,7 +198,6 @@ SECTIONS { . = ALIGN(0x1000); } > consts - /DISCARD/ : { *(.MIPS.abiflags) } /* Everything is statically linked, so discard PLTs. */ diff --git a/src/mips/ps-exe.ld b/src/mips/ps-exe.ld index 096e3710f..124af0dc9 100644 --- a/src/mips/ps-exe.ld +++ b/src/mips/ps-exe.ld @@ -33,7 +33,7 @@ TLOAD_ADDR = DEFINED(TLOAD_ADDR) ? TLOAD_ADDR : 0x80010000; MEMORY { loader : ORIGIN = (TLOAD_ADDR - 0x800), LENGTH = 2048 - ram (rwx) : ORIGIN = 0x80010000, LENGTH = 2M - (TLOAD_ADDR - 0x80000000) + ram (rwx) : ORIGIN = TLOAD_ADDR, LENGTH = 2M - (TLOAD_ADDR - 0x80000000) dcache : ORIGIN = 0x1f800000, LENGTH = 0x400 } diff --git a/src/mips/psyqo/GETTING_STARTED.md b/src/mips/psyqo/GETTING_STARTED.md index 15bf86d2f..e735736e5 100644 --- a/src/mips/psyqo/GETTING_STARTED.md +++ b/src/mips/psyqo/GETTING_STARTED.md @@ -43,7 +43,7 @@ The computer might need to be rebooted after the installation of this script. Next, run the following command to install the proper toolchain and its dependencies: ```cmd -mips install 13.1.0 +mips install 13.2.0 ``` ## Docker diff --git a/src/mips/psyqo/Makefile b/src/mips/psyqo/Makefile index a74e7845f..182ebecc0 100644 --- a/src/mips/psyqo/Makefile +++ b/src/mips/psyqo/Makefile @@ -8,24 +8,9 @@ SRCS = \ ../common/crt0/crt0cxx.s \ ../common/crt0/cxxglue.c \ ../common/syscalls/printf.s \ -src/hardware/cdrom.cpp \ -src/hardware/cpu.cpp \ -src/hardware/gpu.cpp \ -src/hardware/sbus.cpp \ -src/adler32.cpp \ -src/alloc.c \ -src/application.cpp \ -src/cdrom.cpp \ -src/cdrom-device.cpp \ -src/font.cpp \ -src/gpu.cpp \ -src/iso9660-parser.cpp \ -src/kernel.cpp \ -src/msf.cpp \ -src/scene.cpp \ -src/simplepad.cpp \ -src/task.cpp \ -src/xprintf.c \ +$(wildcard src/hardware/*.cpp) \ +$(wildcard src/*.cpp) \ +$(wildcard src/*.c) \ CPPFLAGS = -Werror -I../../../third_party/EASTL/include -I../../../third_party/EABase/include/Common CXXFLAGS = -std=c++20 diff --git a/src/mips/psyqo/bezier.hh b/src/mips/psyqo/bezier.hh new file mode 100644 index 000000000..f50e29ada --- /dev/null +++ b/src/mips/psyqo/bezier.hh @@ -0,0 +1,48 @@ +/* + +MIT License + +Copyright (c) 2023 PCSX-Redux authors + +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 "psyqo/vector.hh" + +namespace psyqo { + +namespace Bezier { + +/** + * @brief Cubic Bezier curve helper function. + * + * @param a Start of the Bezier curve. + * @param b Control point 1. + * @param c Control point 2. + * @param d End of the Bezier curve. + * @param t The point on the curve to sample, from 0.0 to 1.0. + * @return Vec2 The point on the curve at t. + */ +Vec2 cubic(const Vec2& a, const Vec2& b, const Vec2& c, const Vec2& d, FixedPoint<> t); +Vec3 cubic(const Vec3& a, const Vec3& b, const Vec3& c, const Vec3& d, FixedPoint<> t); + +} + +} // namespace psyqo diff --git a/src/mips/psyqo/coroutine.hh b/src/mips/psyqo/coroutine.hh index adc6d2ff1..c56e594f8 100644 --- a/src/mips/psyqo/coroutine.hh +++ b/src/mips/psyqo/coroutine.hh @@ -121,6 +121,9 @@ struct Coroutine { if (!m_handle) return true; bool isDone = m_handle.done(); if (isDone) { + if constexpr (!std::is_void::value) { + m_value = eastl::move(m_handle.promise().m_value); + } m_handle.destroy(); m_handle = nullptr; } @@ -142,8 +145,8 @@ struct Coroutine { private: struct PromiseVoid { - Coroutine get_return_object() { - return Coroutine{eastl::move(std::coroutine_handle::from_promise(*this))}; + Coroutine<> get_return_object() { + return Coroutine<>{eastl::move(std::coroutine_handle::from_promise(*this))}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } @@ -151,15 +154,14 @@ struct Coroutine { void return_void() {} }; struct PromiseValue { - PromiseValue(Coroutine *c) : coroutine(c) {} Coroutine get_return_object() { return Coroutine{eastl::move(std::coroutine_handle::from_promise(*this))}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void unhandled_exception() {} - void return_value(T &&value) { coroutine->m_value = eastl::move(value); } - Coroutine coroutine = nullptr; + void return_value(T &&value) { m_value = eastl::move(value); } + T m_value; }; typedef typename std::conditional::value, PromiseVoid, PromiseValue>::type Promise; Coroutine(std::coroutine_handle &&handle) : m_handle(eastl::move(handle)) {} diff --git a/src/mips/psyqo/examples/bezier/Makefile b/src/mips/psyqo/examples/bezier/Makefile new file mode 100644 index 000000000..7316e8b3d --- /dev/null +++ b/src/mips/psyqo/examples/bezier/Makefile @@ -0,0 +1,10 @@ +TARGET = bezier +TYPE = ps-exe + +SRCS = \ +bezier.cpp \ + +CPPFLAGS = -Werror +CXXFLAGS = -std=c++20 + +include ../../psyqo.mk diff --git a/src/mips/psyqo/examples/bezier/bezier.cpp b/src/mips/psyqo/examples/bezier/bezier.cpp new file mode 100644 index 000000000..33fc5306e --- /dev/null +++ b/src/mips/psyqo/examples/bezier/bezier.cpp @@ -0,0 +1,164 @@ +/* + +MIT License + +Copyright (c) 2023 PCSX-Redux authors + +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 "psyqo/bezier.hh" + +#include "common/syscalls/syscalls.h" +#include "psyqo/application.hh" +#include "psyqo/font.hh" +#include "psyqo/fragments.hh" +#include "psyqo/gpu.hh" +#include "psyqo/primitives/quads.hh" +#include "psyqo/scene.hh" +#include "psyqo/trigonometry.hh" + +using namespace psyqo::fixed_point_literals; +using namespace psyqo::trig_literals; + +namespace { + +class Bezier final : public psyqo::Application { + void prepare() override; + void createScene() override; + + public: + psyqo::Font<> m_font; + psyqo::Trig<> m_trig; +}; + +class BezierScene final : public psyqo::Scene { + void frame() override; + // This will hold the quad fan that make up the Bezier curve. + eastl::array m_quads; + // The two angles representing the control points. + psyqo::Angle m_angle1 = 0.0_pi; + psyqo::Angle m_angle2 = 0.0_pi; + // The step size for the angles. + constexpr static psyqo::Angle c_angleStep1 = 0.0135_pi; + constexpr static psyqo::Angle c_angleStep2 = 0.017_pi; + // The beginning and end points of the Bezier curve. + constexpr static psyqo::Vec2 a = {0.0_fp, 240.0_fp}; + constexpr static psyqo::Vec2 b = {640.0_fp, 240.0_fp}; + + public: + BezierScene() { + // Our quad fan will be made up of 20 quads. All of the points + // at the bottom of the quads are simply following the x axis, + // 32 pixels apart. The y coordinate is always 480, which is + // the height of the screen. The top points are calculated + // every frame using the Bezier curve, except for the last + // point, which is always the end point of the curve. + for (unsigned i = 0; i < 20; i++) { + m_quads[i].setColor({.r = 0xff, .g = 0x80, .b = 0x33}); + m_quads[i].pointC = psyqo::Vertex{{ .x = int16_t(i * 32), .y = 480}}; + m_quads[i].pointD = psyqo::Vertex{{ .x = int16_t(i * 32 + 32), .y = 480}}; + } + m_quads[19].pointB = b; + } +}; + +Bezier bezier; +BezierScene bezierScene; + +} // namespace + +void Bezier::prepare() { + psyqo::GPU::Configuration config; + config.set(psyqo::GPU::Resolution::W640) + .set(psyqo::GPU::VideoMode::AUTO) + .set(psyqo::GPU::ColorMode::C15BITS) + .set(psyqo::GPU::Interlace::INTERLACED); + gpu().initialize(config); +} + +void Bezier::createScene() { + m_font.uploadSystemFont(gpu()); + pushScene(&bezierScene); +} + +void BezierScene::frame() { + // Update the angles of the control points. + m_angle1 += c_angleStep1; + m_angle2 += c_angleStep2; + + // Wrap the angles around if they go over 2pi. + if (m_angle1 > 2.0_pi) { + m_angle1 -= 2.0_pi; + } + if (m_angle2 > 2.0_pi) { + m_angle2 -= 2.0_pi; + } + + // The angles above are used to calculate another set of angles, in order to + // create a smoother animation. This means the speed of the angles will + // follow a sine wave. Angles are divided by 2 because the sine wave goes + // from -1 to 1, and the angles need to go from -pi/2 to pi/2. Remember that + // the Angle class is a fixed point type, with the "1.0" value being equal + // to the value "pi", meaning that the value "pi/2" is equal to "0.5". + auto angle1 = psyqo::Angle(bezier.m_trig.sin(m_angle1)) / 2; + auto angle2 = psyqo::Angle(bezier.m_trig.sin(m_angle2)) / 2; + + // Calculate the control points based on the angles. + auto p1x = bezier.m_trig.cos(angle1) * 200; + auto p1y = bezier.m_trig.sin(angle1) * 200 + 240.0_fp; + auto p2x = bezier.m_trig.cos(angle2) * 200; + auto p2y = bezier.m_trig.sin(angle2) * 200 + 240.0_fp; + + if (p1x < 0) p1x = -p1x; + if (p2x < 0) p2x = -p2x; + + psyqo::Vec2 p1({p1x, p1y}), p2({640.0_fp - p2x, p2y}); + + // Clear the screen. + gpu().clear({{.r = 0x68, .g = 0xb0, .b = 0xd8}}); + + // Calculate the points of the Bezier curve and store them in our Quad fan. + psyqo::FixedPoint<> t; + psyqo::Vertex m = a; + for (int i = 0; i < 19; i++) { + t += 0.05_fp; + m_quads[i].pointA = m; + m = m_quads[i].pointB = psyqo::Bezier::cubic(a, p1, p2, b, t); + } + m_quads[19].pointA = m; + + // Draw the Quad fan. + gpu().sendPrimitive(m_quads); + + +#ifdef DEBUG_BEZIER + // Draw the control points. + psyqo::Prim::Line line; + line.pointA = a; + line.pointB = p1; + gpu().sendPrimitive(line); + line.pointA = p2; + line.pointB = b; + gpu().sendPrimitive(line); +#endif +} + +int main() { return bezier.run(); } diff --git a/src/mips/psyqo/examples/coroutine-demo/coroutine-demo.cpp b/src/mips/psyqo/examples/coroutine-demo/coroutine-demo.cpp index 5951063c1..42ea327ef 100644 --- a/src/mips/psyqo/examples/coroutine-demo/coroutine-demo.cpp +++ b/src/mips/psyqo/examples/coroutine-demo/coroutine-demo.cpp @@ -59,9 +59,10 @@ psyqo::Coroutine<> coroutine(CoroutineDemo *demo) { psyqo::Coroutine<>::Awaiter awaiter = coroutine->awaiter(); bool success = false; bool doneOnce = false; + using namespace psyqo::timer_literals; while (!success) { if (doneOnce) { - demo->gpu().armTimer(demo->gpu().now() + 1 * 1000 * 1000, [coroutine](auto) { coroutine->resume(); }); + demo->gpu().armTimer(demo->gpu().now() + 1_s, [coroutine](auto) { coroutine->resume(); }); co_await awaiter; } doneOnce = true; @@ -82,7 +83,7 @@ psyqo::Coroutine<> coroutine(CoroutineDemo *demo) { doneOnce = false; while (true) { if (doneOnce) { - demo->gpu().armTimer(demo->gpu().now() + 1 * 1000 * 1000, [coroutine](auto) { coroutine->resume(); }); + demo->gpu().armTimer(demo->gpu().now() + 1_s, [coroutine](auto) { coroutine->resume(); }); co_await awaiter; } doneOnce = true; diff --git a/src/mips/psyqo/examples/lines/lines.cpp b/src/mips/psyqo/examples/lines/lines.cpp index 0c07f1c32..b589611a7 100644 --- a/src/mips/psyqo/examples/lines/lines.cpp +++ b/src/mips/psyqo/examples/lines/lines.cpp @@ -54,8 +54,8 @@ class LinesScene final : public psyqo::Scene { }; // We're instantiating the two objects above right now. -Lines Lines; -LinesScene LinesScene; +Lines lines; +LinesScene linesScene; } // namespace @@ -70,7 +70,7 @@ void Lines::prepare() { void Lines::createScene() { m_font.uploadSystemFont(gpu()); - pushScene(&LinesScene); + pushScene(&linesScene); } void LinesScene::frame() { @@ -81,7 +81,7 @@ void LinesScene::frame() { } psyqo::Color bg{{.r = 0, .g = 64, .b = 91}}; bg.r = m_anim; - Lines.gpu().clear(bg); + lines.gpu().clear(bg); if (m_direction) { m_anim++; } else { @@ -94,17 +94,17 @@ void LinesScene::frame() { line1.pointB.x = 300; line1.pointA.y = 50; line1.pointB.y = m_anim; - Lines.gpu().sendPrimitive(line1); + lines.gpu().sendPrimitive(line1); psyqo::Prim::Line line2(psyqo::Color{{.r = 15, .g = 230, .b = 42}}); line2.pointA.x = 50; line2.pointB.x = 300; line2.pointA.y = 200; line2.pointB.y = 255 - m_anim; - Lines.gpu().sendPrimitive(line2); + lines.gpu().sendPrimitive(line2); psyqo::Color c = {{.r = 255, .g = 255, .b = uint8_t(255 - m_anim)}}; - Lines.m_font.print(Lines.gpu(), "Lines!", {{.x = 16, .y = 32}}, c); + lines.m_font.print(lines.gpu(), "Lines!", {{.x = 16, .y = 32}}, c); } -int main() { return Lines.run(); } +int main() { return lines.run(); } diff --git a/src/mips/psyqo/examples/math/Makefile b/src/mips/psyqo/examples/math/Makefile new file mode 100644 index 000000000..668cdcdbf --- /dev/null +++ b/src/mips/psyqo/examples/math/Makefile @@ -0,0 +1,10 @@ +TARGET = math +TYPE = ps-exe + +SRCS = \ +math.cpp \ + +CPPFLAGS = -Werror +CXXFLAGS = -std=c++20 + +include ../../psyqo.mk diff --git a/src/mips/psyqo/examples/math/math.cpp b/src/mips/psyqo/examples/math/math.cpp new file mode 100644 index 000000000..5e30fcda9 --- /dev/null +++ b/src/mips/psyqo/examples/math/math.cpp @@ -0,0 +1,118 @@ +/* + +MIT License + +Copyright (c) 2023 PCSX-Redux authors + +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 "common/syscalls/syscalls.h" +#include "psyqo/application.hh" +#include "psyqo/fixed-point.hh" +#include "psyqo/font.hh" +#include "psyqo/gpu.hh" +#include "psyqo/primitives/quads.hh" +#include "psyqo/scene.hh" +#include "psyqo/trigonometry.hh" + +using namespace psyqo::trig_literals; + +namespace { + +class Math final : public psyqo::Application { + void prepare() override; + void createScene() override; + + public: + psyqo::Font<> m_font; + // We need a trigonometry object to perform the calculations. + // We're using the default template parameters, which means + // that the object will use psyqo::FixedPoint<12, int32_t> as its + // internal representation. This is a 20.12 fixed-point number, + // which will be the results of the calculations. + psyqo::Trig<> m_trig; +}; + +class MathScene final : public psyqo::Scene { + void frame() override; + // This will hold the current angle of the quad. Its constructor + // initializes it to 0. + psyqo::Angle m_angle; + // This is our step size. We'll add it to m_angle every frame. + // Its value is 0.01*Pi radians, or 1.8 degrees. The float literal + // is converted to a psyqo::Angle object by the compiler. + constexpr static psyqo::Angle c_angleStep = 0.01_pi; + // This is the quad we'll draw. + psyqo::Prim::Quad m_quad{{.r = 0xff, .g = 0x80, .b = 0x33}}; +}; + +// We're instantiating the two objects above right now. +Math math; +MathScene mathScene; + +} // namespace + +void Math::prepare() { + psyqo::GPU::Configuration config; + config.set(psyqo::GPU::Resolution::W320) + .set(psyqo::GPU::VideoMode::AUTO) + .set(psyqo::GPU::ColorMode::C15BITS) + .set(psyqo::GPU::Interlace::PROGRESSIVE); + gpu().initialize(config); +} + +void Math::createScene() { + m_font.uploadSystemFont(gpu()); + pushScene(&mathScene); +} + +void MathScene::frame() { + // We'll rotate the quad by a small amount every frame. + m_angle += c_angleStep; + // If the angle is greater than 2*Pi, we'll subtract 2*Pi from it. + // This is to prevent the angle from growing too large and overflowing, + // as it is still an integer internally. + if (m_angle >= 2.0_pi) { + m_angle -= 2.0_pi; + } + + // This is the vertex we'll rotate. + constexpr auto c_vertex = psyqo::Vertex{{.x = 100, .y = 0}}; + + // Perform a rotation of c_vertex by m_angle. This is the standard + // rotation matrix multiplication. + auto cos = math.m_trig.cos(m_angle); + auto sin = math.m_trig.sin(m_angle); + auto x = (c_vertex.x * cos - c_vertex.y * sin).integer(); + auto y = (c_vertex.x * sin + c_vertex.y * cos).integer(); + + // Then draw a quad with the rotated vertex. + m_quad.pointA = {{.x = static_cast(x + 160), .y = static_cast(y + 120)}}; + m_quad.pointB = {{.x = static_cast(-y + 160), .y = static_cast(x + 120)}}; + m_quad.pointC = {{.x = static_cast(y + 160), .y = static_cast(-x + 120)}}; + m_quad.pointD = {{.x = static_cast(-x + 160), .y = static_cast(-y + 120)}}; + + // Clear the screen and draw the quad. + gpu().clear({{.r = 0x68, .g = 0xb0, .b = 0xd8}}); + gpu().sendPrimitive(m_quad); +} + +int main() { return math.run(); } diff --git a/src/mips/psyqo/examples/task-demo/task-demo.cpp b/src/mips/psyqo/examples/task-demo/task-demo.cpp index a906ba225..90d5f7c01 100644 --- a/src/mips/psyqo/examples/task-demo/task-demo.cpp +++ b/src/mips/psyqo/examples/task-demo/task-demo.cpp @@ -119,7 +119,8 @@ void TaskDemo::createScene() { .butCatch([this](auto queue) { m_text = "Failure, retrying..."; syscall_puts("Failure, retrying...\n"); - gpu().armTimer(gpu().now() + 1 * 1000 * 1000, [queue](auto) { queue->run(); }); + using namespace psyqo::timer_literals; + gpu().armTimer(gpu().now() + 1_s, [queue](auto) { queue->run(); }); }) .run(); } diff --git a/src/mips/psyqo/examples/timers/timers.cpp b/src/mips/psyqo/examples/timers/timers.cpp index 50404747b..76fd8c115 100644 --- a/src/mips/psyqo/examples/timers/timers.cpp +++ b/src/mips/psyqo/examples/timers/timers.cpp @@ -111,7 +111,8 @@ void TimersScene::start(Scene::StartReason reason) { }); if (reason == Scene::StartReason::Create) { // If we are getting created, create a 500ms periodic timer too. - m_timerId = timers.gpu().armPeriodicTimer(500'000, [this](auto) { m_fasterCounter++; }); + using namespace psyqo::timer_literals; + m_timerId = timers.gpu().armPeriodicTimer(500_ms, [this](auto) { m_fasterCounter++; }); } else { // Otherwise, if we're getting resumed, we're going to resume our timer. timers.gpu().resumeTimer(m_timerId); diff --git a/src/mips/psyqo/fixed-point.hh b/src/mips/psyqo/fixed-point.hh new file mode 100644 index 000000000..916609f53 --- /dev/null +++ b/src/mips/psyqo/fixed-point.hh @@ -0,0 +1,406 @@ +/* + +MIT License + +Copyright (c) 2023 PCSX-Redux authors + +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. + +*/ + +#pragma once + +#include +#include + +#include +#include +#include + +namespace psyqo { + +namespace FixedPointInternals { + +uint32_t iDiv(uint64_t rem, uint32_t base, unsigned precisionBits); +int32_t dDiv(int32_t a, int32_t b, unsigned precisionBits); +void printInt(uint32_t value, eastl::function&, unsigned precisionBits); + +} // namespace FixedPointInternals + +/** + * @brief Fixed point number type. + * + * @details This is a fixed point number type with a configurable number + * of fractional bits. The default is 12 fractional bits, which is + * suitable for representing 3D coordinates in the PSX's 20.12 fixed + * point format. The number of fractional bits can be changed by + * specifying a different value for the template parameter + * `precisionBits`. The underlying integer type can also be changed + * by specifying a different type for the template parameter `T`. + * The default is `int32_t`, but `int16_t`, `uint32_t`, `uint16_t` + * are also supported. The template behaves as a value type, and + * supports most of the usual arithmetic operators. Its size is the + * same as the underlying integer type. + * + * @tparam precisionBits The number of fractional bits to use. + * @tparam T The underlying integer type to use. + */ +template +class FixedPoint { + using signedUpType = std::conditional::type; + using unsignedUpType = std::conditional::type; + using upType = std::conditional::value, signedUpType, unsignedUpType>::type; + + public: + /** + * @brief The raw value of the fixed point number. + * + * @details This is the raw value of the fixed point number, and + * can be used to access the underlying integer type directly, which + * can be useful for some operations. + */ + T value; + + /** + * @brief The scale of the fixed point number. + * + */ + static constexpr unsigned scale = 1 << precisionBits; + + /** + * @brief Constructs a fixed point number from an integer and a + * fraction. + */ + explicit constexpr FixedPoint(T integer, T fraction) : value(integer * scale + fraction) { + static_assert(sizeof(T) == 4 || sizeof(T) == 2); + static_assert(precisionBits > 0); + } + + /** + * @brief Constructs a fixed point number from a floating point + * number. + * + * @details Note that this is a `consteval` function, so it can + * only be used with compile-time constants. This is intentional, + * as the conversion from floating point to fixed point is + * basically impossible to do at runtime without using floating + * point arithmetic, which is not available on the PSX. + */ + consteval FixedPoint(long double ld) { + static_assert(std::is_signed::value || (ld >= 0)); + bool negative = ld < 0; + T integer = negative ? -ld : ld; + T fraction = ld * scale - integer * scale + (negative ? -0.5 : 0.5); + value = integer * scale + fraction; + } + + constexpr FixedPoint() : value(0) {} + constexpr FixedPoint(const FixedPoint&) = default; + constexpr FixedPoint(FixedPoint&&) = default; + constexpr FixedPoint& operator=(const FixedPoint&) = default; + + /** + * @brief Construct a new Fixed Point number from a different + * fixed point number. + */ + template + explicit FixedPoint(FixedPoint other) { + if constexpr (precisionBits == otherPrecisionBits) { + *this = other; + } else if constexpr (precisionBits > otherPrecisionBits) { + value = other.value << (precisionBits - otherPrecisionBits); + } else if constexpr (precisionBits < otherPrecisionBits) { + value = other.value >> (otherPrecisionBits - precisionBits); + } + } + + /** + * @brief Returns the integer part of the fixed point number. + * + * @details This returns the integer part of the fixed point, + * rounded to the nearest integer. Note that this is not the same + * as truncating the fixed point number, as it rounds to the + * nearest integer, rather than towards zero. + * + * @return constexpr T The integer part of the fixed point number. + */ + constexpr T integer() const { + if constexpr (std::is_signed::value) { + if (value < 0) { + return (value - scale / 2) / scale; + } + } + return (value + scale / 2) / scale; + } + + template + constexpr U integer() const { + if constexpr (std::is_signed::value) { + if (value < 0) { + return U((value - scale / 2) / scale); + } + } + return U((value + scale / 2) / scale); + } + + /** + * @brief Prints out the fixed point number. + * + * @details This prints out the fixed point number using the provided + * function for emitting characters out. Note that the formatting is + * pretty basic for now, and only supports printing out the number in + * decimal format, with no padding or precision setting. Maximum + * displayed precision is 5 decimal places. + * + * @param charPrinter A function that prints a single character, to + * be used to print the fixed point number. + */ + void print(eastl::function& charPrinter) const { + T copy = value; + if constexpr (std::is_signed::value) { + if (copy < 0) { + charPrinter('-'); + copy = -copy; + } + } + FixedPointInternals::printInt(copy, charPrinter, precisionBits); + } + + constexpr FixedPoint operator+(FixedPoint other) const { + FixedPoint ret = *this; + ret.value += other.value; + return ret; + } + + template + constexpr FixedPoint operator+(U other) const { + FixedPoint ret = *this; + ret.value += other * scale; + return ret; + } + + constexpr FixedPoint operator-(FixedPoint other) const { + FixedPoint ret = *this; + ret.value -= other.value; + return ret; + } + + template + constexpr FixedPoint operator-(U other) const { + FixedPoint ret = *this; + ret.value -= other * scale; + return ret; + } + + constexpr FixedPoint operator*(FixedPoint other) const { + upType t = value; + t *= other.value; + t /= scale; + FixedPoint ret; + ret.value = t; + return ret; + } + + template + constexpr FixedPoint operator*(U other) const { + FixedPoint ret = *this; + ret.value *= other; + return ret; + } + + constexpr FixedPoint operator/(FixedPoint other) const { + FixedPoint ret; + if constexpr (sizeof(T) == 4) { + if constexpr (std::is_signed::value) { + ret.value = FixedPointInternals::iDiv(value, other.value); + } else if constexpr (!std::is_signed::value) { + ret.value = FixedPointInternals::dDiv(value, other.value); + } + } else if constexpr (sizeof(T) == 2) { + upType t = value; + t *= scale; + t /= other.value; + ret.value = t; + } + return ret; + } + + template + constexpr FixedPoint operator/(U other) const { + FixedPoint ret = *this; + ret.value /= other; + return ret; + } + + constexpr FixedPoint operator-() const { + FixedPoint ret = *this; + ret.value = -ret.value; + return ret; + } + + constexpr FixedPoint& operator+=(FixedPoint other) { + value += other.value; + return *this; + } + + template + constexpr FixedPoint& operator+=(U other) { + value += other * scale; + return *this; + } + + constexpr FixedPoint& operator-=(FixedPoint other) { + value -= other.value; + return *this; + } + + template + constexpr FixedPoint& operator-=(U other) { + value -= other * scale; + return *this; + } + + constexpr FixedPoint& operator*=(FixedPoint other) { + upType t = value; + t *= other.value; + t /= precisionBits; + value = t; + return *this; + } + + template + constexpr FixedPoint& operator*=(U other) { + value *= other; + return *this; + } + + constexpr FixedPoint& operator/=(FixedPoint other) { + if constexpr (sizeof(T) == 4) { + if constexpr (std::is_signed::value) { + value = FixedPointInternals::iDiv(value, other.value); + } else if constexpr (!std::is_signed::value) { + value = FixedPointInternals::dDiv(value, other.value); + } + } else if constexpr (sizeof(T) == 2) { + upType t = value; + t *= scale; + t /= other.value; + value = t; + } + return *this; + } + + template + constexpr FixedPoint& operator/=(U other) { + value /= other; + return *this; + } + + auto operator<=>(const FixedPoint& other) const = default; + + constexpr FixedPoint operator<<(unsigned shift) const { + FixedPoint ret = *this; + ret.value <<= shift; + return ret; + } + + constexpr FixedPoint operator>>(unsigned shift) const { + FixedPoint ret = *this; + ret.value >>= shift; + return ret; + } + + constexpr FixedPoint& operator<<=(unsigned shift) { + value <<= shift; + return *this; + } + + constexpr FixedPoint& operator>>=(unsigned shift) { + value >>= shift; + return *this; + } + + constexpr FixedPoint operator++() { + value += scale; + return *this; + } + + constexpr FixedPoint operator++(int) { + FixedPoint ret = *this; + value += scale; + return ret; + } + + constexpr FixedPoint operator--() { + value -= scale; + return *this; + } + + constexpr FixedPoint operator--(int) { + FixedPoint ret = *this; + value -= scale; + return ret; + } + + constexpr bool operator!() const { return value == 0; } +}; + +template +constexpr FixedPoint operator+(U a, FixedPoint b) { + return b + a; +} + +template +constexpr FixedPoint operator-(U a, FixedPoint b) { + return -b + a; +} + +template +constexpr FixedPoint operator*(U a, FixedPoint b) { + return b * a; +} + +template +constexpr FixedPoint operator/(U a, FixedPoint b) { + FixedPoint ret; + if constexpr (sizeof(T) == 4) { + if constexpr (std::is_signed::value || std::is_signed::value) { + ret.set(FixedPointInternals::iDiv(a, b.get())); + } else if constexpr (!std::is_signed::value && !std::is_signed::value) { + ret.set(FixedPointInternals::dDiv(a, b.get())); + } + } else if constexpr (sizeof(T) == 2) { + ret.set(a * FixedPoint::scale / b.get()); + } + return ret; +} + +namespace fixed_point_literals { + +/** + * @brief User-defined literal for constructing a 20.12 fixed point number. + * + * @param value The value to construct the fixed point number from. + * @return consteval FixedPoint<> The constructed fixed point number. + */ +consteval FixedPoint<> operator""_fp(long double value) { return value; } + +} // namespace fixed_point_literals + +} // namespace psyqo diff --git a/src/mips/psyqo/gpu.hh b/src/mips/psyqo/gpu.hh index 7b084a187..14a6f01fe 100644 --- a/src/mips/psyqo/gpu.hh +++ b/src/mips/psyqo/gpu.hh @@ -46,6 +46,23 @@ enum DmaCallback { } +namespace timer_literals { + +/** + * @brief Literal operators for time units. + * + * @details These operators can be used to specify time units suitable + * for the GPU's `armTimer` and `armPeriodicTimer` methods. For example, + * `gpu().armPeriodicTimer(1_s, callback)` will create a timer that + * fires every second. + */ +constexpr uint32_t operator""_ns(unsigned long long int value) { return value / 1'000; } +constexpr uint32_t operator""_us(unsigned long long int value) { return value; } +constexpr uint32_t operator""_ms(unsigned long long int value) { return value * 1'000; } +constexpr uint32_t operator""_s(unsigned long long int value) { return value * 1'000'000; } + +} // namespace timer_literals + /** * @brief The singleton GPU class. * @@ -220,10 +237,14 @@ class GPU { static void waitReady(); /** - * @brief Sends a raw 32 bits value to the GP0 register of the GPU. + * @brief Waits until the GPU's FIFO is ready to receive data. + */ + static void waitFifo(); + + /** + * @brief Sends a raw 32 bits value to the Data register of the GPU. */ static void sendRaw(uint32_t data) { Hardware::GPU::Data = data; } - template /** * @brief Sends a primitive to the GPU. This is a blocking call. @@ -231,12 +252,14 @@ class GPU { * @details This method will immediately send the specified primitive to the GPU. * @param primitive The primitive to send to the GPU. */ + template static void sendPrimitive(const Primitive &primitive) { static_assert((sizeof(Primitive) % 4) == 0, "Primitive's size must be a multiple of 4"); waitReady(); const uint32_t *ptr = reinterpret_cast(&primitive); size_t size = sizeof(Primitive) / sizeof(uint32_t); for (int i = 0; i < size; i++) { + if constexpr (sizeof(Primitive) > 56) waitFifo(); sendRaw(*ptr++); } } diff --git a/src/mips/psyqo/internal/gpu/configuration.hh b/src/mips/psyqo/internal/gpu/configuration.hh index 57ebd025c..62d16e593 100644 --- a/src/mips/psyqo/internal/gpu/configuration.hh +++ b/src/mips/psyqo/internal/gpu/configuration.hh @@ -77,6 +77,7 @@ struct psyqo::GPU::Configuration { } Configuration &set(Interlace interlace) { config.videoInterlace = interlace == Interlace::INTERLACED ? VI_ON : VI_OFF; + config.vResolution = interlace == Interlace::INTERLACED ? VR_480 : VR_240; return *this; } diff --git a/src/mips/psyqo/kernel.hh b/src/mips/psyqo/kernel.hh index 05c3fcf9f..c26e3b83d 100644 --- a/src/mips/psyqo/kernel.hh +++ b/src/mips/psyqo/kernel.hh @@ -89,7 +89,7 @@ enum class DMA : unsigned { /** * @brief Stops the execution of the application. */ -void abort(const char* msg); +[[noreturn]] void abort(const char* msg); /** * @brief A C++ wrapper around the `openEvent` syscall. diff --git a/src/mips/psyqo/primitives/lines.hh b/src/mips/psyqo/primitives/lines.hh index 0d8d9c02b..c5782e084 100644 --- a/src/mips/psyqo/primitives/lines.hh +++ b/src/mips/psyqo/primitives/lines.hh @@ -28,6 +28,8 @@ SOFTWARE. #include +#include + #include "psyqo/primitives/common.hh" namespace psyqo { @@ -106,6 +108,87 @@ struct GouraudLine { }; static_assert(sizeof(GouraudLine) == sizeof(uint32_t) * 4, "Line is not 4 words"); +/** + * @brief The primitive used to begin a polyline. + * + * @details This primitive is used to begin a flat-color polyline. As polylines + * are drawn using multiple Line primitives, this primitive is used to set the + * color of the polyline and the first vertex. As polylines can be complex, + * this allows drawing them piece by piece, iteratively. After sending this + * primitive, you need to send a number of Vertex structures using the GPU's + * `sendRaw` method, and then send a PolyLineEnd struct to finish the polyline. + * Note that it may be necessary to use the `waitFifo` method of the GPU to + * ensure that the GPU has finished processing the previous vertex data before + * sending the next one. + */ +struct PolyLineBegin { + PolyLineBegin() : command(0x48000000) {} + PolyLineBegin(Color c) : command(0x48000000 | c.packed) {} + PolyLineBegin& setColor(Color c) { + uint32_t wasSemiTrans = command & 0x02000000; + command = 0x48000000 | c.packed | wasSemiTrans; + return *this; + } + PolyLineBegin& setOpaque() { + command &= ~0x02000000; + return *this; + } + PolyLineBegin& setSemiTrans() { + command |= 0x02000000; + return *this; + } + + private: + uint32_t command; + + public: + Vertex point; +}; +static_assert(sizeof(PolyLineBegin) == sizeof(uint32_t) * 2, "PolyLineBegin is not 2 words"); + +struct PolyLineEnd { + const uint32_t endMarker = 0x50005000; +}; + +/** + * @brief The primitive used to draw a polyline. + * + * @details This primitive is used to draw a flat-color polyline. This variant + * of the primitive is used when the number of segments in the polyline is + * known at compile time. If the number of segments is not known at compile + * time, use the `PolyLineBegin` mechanism instead. + * + * @tparam N The number of segments in the polyline. + */ + +template +struct PolyLine { + PolyLine() : command(0x48000000) {} + PolyLine(Color c) : command(0x48000000 | c.packed) {} + PolyLine& setColor(Color c) { + uint32_t wasSemiTrans = command & 0x02000000; + command = 0x48000000 | c.packed | wasSemiTrans; + return *this; + } + PolyLine& setOpaque() { + command &= ~0x02000000; + return *this; + } + PolyLine& setSemiTrans() { + command |= 0x02000000; + return *this; + } + + private: + uint32_t command; + + public: + eastl::array points; + + private: + const uint32_t endMarker = 0x50005000; +}; + } // namespace Prim } // namespace psyqo diff --git a/src/mips/psyqo/primitives/quads.hh b/src/mips/psyqo/primitives/quads.hh index 0d3dae2f2..ffc77263d 100644 --- a/src/mips/psyqo/primitives/quads.hh +++ b/src/mips/psyqo/primitives/quads.hh @@ -63,6 +63,22 @@ struct Quad { command |= 0x02000000; return *this; } + Quad& setPointA(Vertex v) { + pointA = v; + return *this; + } + Quad& setPointB(Vertex v) { + pointB = v; + return *this; + } + Quad& setPointC(Vertex v) { + pointC = v; + return *this; + } + Quad& setPointD(Vertex v) { + pointD = v; + return *this; + } private: uint32_t command; @@ -150,6 +166,22 @@ struct GouraudQuad { command |= 0x02000000; return *this; } + GouraudQuad& setPointA(Vertex v) { + pointA = v; + return *this; + } + GouraudQuad& setPointB(Vertex v) { + pointB = v; + return *this; + } + GouraudQuad& setPointC(Vertex v) { + pointC = v; + return *this; + } + GouraudQuad& setPointD(Vertex v) { + pointD = v; + return *this; + } private: uint32_t command; diff --git a/src/mips/psyqo/primitives/triangles.hh b/src/mips/psyqo/primitives/triangles.hh index 929c385b2..6e1b5789a 100644 --- a/src/mips/psyqo/primitives/triangles.hh +++ b/src/mips/psyqo/primitives/triangles.hh @@ -56,6 +56,18 @@ struct Triangle { command |= 0x02000000; return *this; } + Triangle& setPointA(Vertex v) { + pointA = v; + return *this; + } + Triangle& setPointB(Vertex v) { + pointB = v; + return *this; + } + Triangle& setPointC(Vertex v) { + pointC = v; + return *this; + } private: uint32_t command; @@ -137,6 +149,18 @@ struct GouraudTriangle { command |= 0x02000000; return *this; } + GouraudTriangle& setPointA(Vertex v) { + pointA = v; + return *this; + } + GouraudTriangle& setPointB(Vertex v) { + pointB = v; + return *this; + } + GouraudTriangle& setPointC(Vertex v) { + pointC = v; + return *this; + } private: uint32_t command; diff --git a/src/mips/psyqo/scene.hh b/src/mips/psyqo/scene.hh index dc98c29fa..1959118ab 100644 --- a/src/mips/psyqo/scene.hh +++ b/src/mips/psyqo/scene.hh @@ -26,9 +26,10 @@ SOFTWARE. #pragma once +#include "psyqo/application.hh" + namespace psyqo { -class Application; class GPU; /** @@ -81,17 +82,17 @@ class Scene { /** * @brief Alias for `Application::pushScene`. */ - void pushScene(Scene* scene); + void pushScene(Scene* scene) { m_parent->pushScene(scene); } /** * @brief Alias for `Application::popScene`. */ - Scene* popScene(); + Scene* popScene() { return m_parent->popScene(); } /** * @brief Alias for `Application::gpu()`. */ - psyqo::GPU& gpu(); + psyqo::GPU& gpu() { return m_parent->gpu(); } private: Application* m_parent; diff --git a/src/mips/psyqo/src/bezier.cpp b/src/mips/psyqo/src/bezier.cpp new file mode 100644 index 000000000..89a120ffe --- /dev/null +++ b/src/mips/psyqo/src/bezier.cpp @@ -0,0 +1,70 @@ +/* + +MIT License + +Copyright (c) 2023 PCSX-Redux authors + +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 "psyqo/bezier.hh" + +psyqo::Vec2 psyqo::Bezier::cubic(const psyqo::Vec2& a, const psyqo::Vec2& b, const psyqo::Vec2& c, const psyqo::Vec2& d, + psyqo::FixedPoint<> t) { + using namespace psyqo::fixed_point_literals; + FixedPoint<> t2 = t * t; + FixedPoint<> t3 = t2 * t; + + FixedPoint<> mt = 1.0_fp - t; + FixedPoint<> mt2 = mt * mt; + FixedPoint<> mt3 = mt2 * mt; + + FixedPoint<> f1 = mt3; + FixedPoint<> f2 = mt2 * t * 3; + FixedPoint<> f3 = mt * t2 * 3; + FixedPoint<> f4 = t3; + + FixedPoint<> x = a.x * f1 + b.x * f2 + c.x * f3 + d.x * f4; + FixedPoint<> y = a.y * f1 + b.y * f2 + c.y * f3 + d.y * f4; + + return {x, y}; +} + +psyqo::Vec3 psyqo::Bezier::cubic(const psyqo::Vec3& a, const psyqo::Vec3& b, const psyqo::Vec3& c, const psyqo::Vec3& d, + psyqo::FixedPoint<> t) { + using namespace psyqo::fixed_point_literals; + FixedPoint<> t2 = t * t; + FixedPoint<> t3 = t2 * t; + + FixedPoint<> mt = 1.0_fp - t; + FixedPoint<> mt2 = mt * mt; + FixedPoint<> mt3 = mt2 * mt; + + FixedPoint<> f1 = mt3; + FixedPoint<> f2 = mt2 * t * 3; + FixedPoint<> f3 = mt * t2 * 3; + FixedPoint<> f4 = t3; + + FixedPoint<> x = a.x * f1 + b.x * f2 + c.x * f3 + d.x * f4; + FixedPoint<> y = a.y * f1 + b.y * f2 + c.y * f3 + d.y * f4; + FixedPoint<> z = a.z * f1 + b.z * f2 + c.z * f3 + d.z * f4; + + return {x, y, z}; +} diff --git a/src/mips/psyqo/src/scene.cpp b/src/mips/psyqo/src/eastl-glue.cpp similarity index 77% rename from src/mips/psyqo/src/scene.cpp rename to src/mips/psyqo/src/eastl-glue.cpp index b5d13c30c..a69db1abf 100644 --- a/src/mips/psyqo/src/scene.cpp +++ b/src/mips/psyqo/src/eastl-glue.cpp @@ -2,7 +2,7 @@ MIT License -Copyright (c) 2022 PCSX-Redux authors +Copyright (c) 2023 PCSX-Redux authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -24,12 +24,8 @@ SOFTWARE. */ -#include "psyqo/scene.hh" +#include "psyqo/xprintf.h" -#include "psyqo/application.hh" - -void psyqo::Scene::pushScene(Scene* scene) { m_parent->pushScene(scene); } - -psyqo::Scene* psyqo::Scene::popScene() { return m_parent->popScene(); } - -psyqo::GPU& psyqo::Scene::gpu() { return m_parent->gpu(); } +int Vsnprintf8(char* pDestination, size_t n, const char* pFormat, va_list arguments) { + return vsnprintf(pDestination, n, pFormat, arguments); +} diff --git a/src/mips/psyqo/src/fixed-point.cpp b/src/mips/psyqo/src/fixed-point.cpp new file mode 100644 index 000000000..f7214a7f6 --- /dev/null +++ b/src/mips/psyqo/src/fixed-point.cpp @@ -0,0 +1,100 @@ +/* + +MIT License + +Copyright (c) 2023 PCSX-Redux authors + +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 "psyqo/fixed-point.hh" + +uint32_t psyqo::FixedPointInternals::iDiv(uint64_t rem, uint32_t base, unsigned precisionBits) { + rem <<= precisionBits; + uint64_t b = base; + uint64_t res, d = 1; + uint32_t high = rem >> 32; + + res = 0; + if (high >= base) { + high /= base; + res = static_cast(high) << 32; + rem -= static_cast(high * base) << 32; + } + + while (static_cast(b) > 0 && b < rem) { + b = b + b; + d = d + d; + } + + do { + if (rem >= b) { + rem -= b; + res += d; + } + b >>= 1; + d >>= 1; + } while (d); + + return res; +} + +int32_t psyqo::FixedPointInternals::dDiv(int32_t a, int32_t b, unsigned precisionBits) { + int s = 1; + if (a < 0) { + a = -a; + s = -1; + } + if (b < 0) { + b = -b; + s = -s; + } + return iDiv(a, b, precisionBits) * s; +} + +void psyqo::FixedPointInternals::printInt(uint32_t value, eastl::function& charPrinter, + unsigned precisionBits) { + uint32_t integer = value >> precisionBits; + uint64_t fractional = value - (integer << precisionBits); + if (integer == 0) { + charPrinter('0'); + } else { + char out[12]; + char* ptr = out; + while (integer) { + auto digit = integer % 10; + integer /= 10; + *ptr++ = digit + '0'; + } + while (ptr != out) { + charPrinter(*--ptr); + } + } + if (fractional == 0) return; + charPrinter('.'); + for (unsigned i = 0; i < 5; i++) { + fractional *= 10; + uint64_t copy = fractional; + copy >>= precisionBits; + fractional -= copy << precisionBits; + charPrinter((copy % 10) + '0'); + if (fractional == 0) return; + } +} diff --git a/src/mips/psyqo/src/font.cpp b/src/mips/psyqo/src/font.cpp index 28393be6b..ccda2091c 100644 --- a/src/mips/psyqo/src/font.cpp +++ b/src/mips/psyqo/src/font.cpp @@ -30,7 +30,7 @@ SOFTWARE. #include #include "psyqo/gpu.hh" -#include "system-font.c" +#include "system-font.inc" void psyqo::FontBase::uploadSystemFont(psyqo::GPU& gpu) { const Vertex clutPosition = {{.x = 960, .y = 464}}; diff --git a/src/mips/psyqo/src/gpu.cpp b/src/mips/psyqo/src/gpu.cpp index 9885105b6..c98957da0 100644 --- a/src/mips/psyqo/src/gpu.cpp +++ b/src/mips/psyqo/src/gpu.cpp @@ -43,17 +43,30 @@ void psyqo::GPU::waitReady() { ; } +void psyqo::GPU::waitFifo() { + while ((Hardware::GPU::Ctrl & uint32_t(0x02000000)) == 0) + ; +} + void psyqo::GPU::initialize(const psyqo::GPU::Configuration &config) { // Reset Hardware::GPU::Ctrl = 0; + // FIFO polling mode + Hardware::GPU::Ctrl = 0x04000001; // Display Mode Hardware::GPU::Ctrl = 0x08000000 | (config.config.hResolution << 0) | (config.config.vResolution << 2) | (config.config.videoMode << 3) | (config.config.colorDepth << 4) | (config.config.videoInterlace << 5) | (config.config.hResolutionExtended << 6); // Horizontal Range Hardware::GPU::Ctrl = 0x06000000 | 0x260 | (0xc60 << 12); + // Vertical Range - Hardware::GPU::Ctrl = 0x07000000 | 16 | (255 << 10); + if (config.config.videoMode == Configuration::VM_NTSC) { + Hardware::GPU::Ctrl = 0x07000000 | 16 | (255 << 10); + } else { + Hardware::GPU::Ctrl = 0x07046c2b; + } + // Display Area Hardware::GPU::Ctrl = 0x05000000; @@ -109,7 +122,7 @@ void psyqo::GPU::initialize(const psyqo::GPU::Configuration &config) { Kernel::enableDma(Kernel::DMA::GPU); Kernel::registerDmaEvent(Kernel::DMA::GPU, [this]() { // DMA disabled - Hardware::GPU::Ctrl = 0x04000000; + Hardware::GPU::Ctrl = 0x04000001; eastl::atomic_signal_fence(eastl::memory_order_acquire); if (m_flushCacheAfterDMA) { Prim::FlushCache fc; @@ -148,13 +161,9 @@ void psyqo::GPU::flip() { pumpCallbacks(); eastl::atomic_signal_fence(eastl::memory_order_acquire); } while ((m_previousFrameCount == m_frameCount) || (m_chainStatus == CHAIN_TRANSFERRING)); - m_chainStatus = CHAIN_IDLE; - eastl::atomic_signal_fence(eastl::memory_order_release); - m_previousFrameCount = m_frameCount; auto parity = m_parity; parity ^= 1; - m_parity = parity; if (!m_interlaced) { bool firstBuffer = !parity; // Set Display Area @@ -163,7 +172,22 @@ void psyqo::GPU::flip() { } else { Hardware::GPU::Ctrl = 0x05000000; } + } else if (!pcsx_present()) { + while (1) { + uint32_t stat = Hardware::GPU::Ctrl; + int isDrawingEven = (stat & 0x80000000) == 0; + int isMaskingEven = (stat & 0x00002000) == 0; + if (parity && isDrawingEven && !isMaskingEven) break; + if (!parity && !isDrawingEven && isMaskingEven) break; + pumpCallbacks(); + } } + + m_chainStatus = CHAIN_IDLE; + m_parity = parity; + m_previousFrameCount = m_frameCount; + eastl::atomic_signal_fence(eastl::memory_order_release); + enableScissor(); Kernel::Internal::beginFrame(); if (m_chainHead) { diff --git a/src/mips/psyqo/src/kernel.cpp b/src/mips/psyqo/src/kernel.cpp index 1be95b641..0108980d5 100644 --- a/src/mips/psyqo/src/kernel.cpp +++ b/src/mips/psyqo/src/kernel.cpp @@ -72,11 +72,13 @@ KernelEventFunction allocateEventFunction(eastl::function&& lambda) { } // namespace -void psyqo::Kernel::abort(const char* msg) { +[[noreturn]] void psyqo::Kernel::abort(const char* msg) { + fastEnterCriticalSection(); pcsx_message(msg); pcsx_debugbreak(); - while (1) - ; + syscall_puts(msg); + syscall_putchar('\n'); + while (1) asm(""); } uint32_t psyqo::Kernel::openEvent(uint32_t classId, uint32_t spec, uint32_t mode, eastl::function&& lambda) { diff --git a/src/mips/psyqo/src/system-font.c b/src/mips/psyqo/src/system-font.inc similarity index 100% rename from src/mips/psyqo/src/system-font.c rename to src/mips/psyqo/src/system-font.inc diff --git a/src/mips/psyqo/src/trigonometry.cpp b/src/mips/psyqo/src/trigonometry.cpp new file mode 100644 index 000000000..0f9df59ca --- /dev/null +++ b/src/mips/psyqo/src/trigonometry.cpp @@ -0,0 +1,59 @@ +/* + +MIT License + +Copyright (c) 2023 PCSX-Redux authors + +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 "psyqo/trigonometry.hh" + +// This is a table generator for cos(n * 2pi / 2048) * 2^24 +// The table is 512 entries long, from cos(0) to cos(pi/2). +// The table is generated by the following recurrence relation: +// f(n) = cos(n * 2pi / 2048) +// f(n) = 2 * f(1) * f(n - 1) - f(n - 2) +void psyqo::TrigInternals::generateTable(eastl::array& table, unsigned precisionBits) { + // 2^24 * cos(0 * 2pi / 2048) + table[0] = 16777216; + // 2^24 * cos(1 * 2pi / 2048) = C = f(1) + constexpr int64_t C = 16777137; + table[1] = C; + + for (int i = 2; i < 511; i++) { + table[i] = ((C * table[i - 1]) >> 23) - table[i - 2]; + } + + // The approximation is a bit too steep, so this value would otherwise + // get slightly negative + table[511] = 0; + + // Adjusts the precision of the table + if (precisionBits > 24) { + for (int i = 0; i < 512; i++) { + table[i] <<= (precisionBits - 24); + } + } else if (precisionBits < 24) { + for (int i = 0; i < 512; i++) { + table[i] >>= (24 - precisionBits); + } + } +} diff --git a/src/mips/psyqo/trigonometry.hh b/src/mips/psyqo/trigonometry.hh new file mode 100644 index 000000000..ad990be4a --- /dev/null +++ b/src/mips/psyqo/trigonometry.hh @@ -0,0 +1,122 @@ +/* + +MIT License + +Copyright (c) 2023 PCSX-Redux authors + +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. + +*/ + +#pragma once + +#include + +#include +#include "psyqo/fixed-point.hh" + +namespace psyqo { + +namespace TrigInternals { + +void generateTable(eastl::array& table, unsigned precisionBits); + +} + +/** + * @brief A fixed point angle. + * + * @details This is a fixed point angle. Its value is in fractions of Pi. + * In other words, 1.0 is 180 degrees, 0.5 is 90 degrees, and so on. + */ +typedef FixedPoint<10> Angle; + +namespace trig_literals { + +/** + * @brief A user-defined literal for angle values. + * + * @param angle The angle in fractions of Pi. + * @return consteval Angle The constructed angle. + */ +consteval Angle operator""_pi(long double angle) { return angle; } + +} // namespace trig_literals + +/** + * @brief A trigonometry table. + * + * @details This is a trigonometry table. It is used to calculate + * sine and cosine values for angles. It is constructed with a + * template parameter that specifies the number of bits of + * precision to use for the table. The default is 12 bits. + * + * @tparam precisionBits The number of bits of precision to use + * for the FixedPoint values in the table. + */ +template +class Trig { + public: + Trig() { TrigInternals::generateTable(table, precisionBits); } + + /** + * @brief Calculate the cosine of an angle. + * + * @param a The angle to calculate the cosine of. + * @return FixedPoint The cosine of the angle. + */ + FixedPoint cos(Angle a) const { + using namespace trig_literals; + uint32_t t = a.value; + + t %= (2.0_pi).value; + a.value = t; + int32_t r; + + if (a < 0.5_pi) { + r = table[t]; + } else if (a < 1.0_pi) { + r = -table[(1.0_pi).value - 1 - t]; + } else if (a < 1.5_pi) { + r = -table[t - (1.0_pi).value]; + } else { + r = table[(2.0_pi).value - 1 - t]; + } + + FixedPoint ret; + ret.value = r; + return ret; + } + + /** + * @brief Calculate the sine of an angle. + * + * @param a The angle to calculate the sine of. + * @return FixedPoint The sine of the angle. + */ + FixedPoint sin(Angle a) const { + using namespace trig_literals; + return cos(a - 0.5_pi); + } + + private: + eastl::array table; +}; + +} // namespace psyqo diff --git a/src/mips/psyqo/vector.hh b/src/mips/psyqo/vector.hh new file mode 100644 index 000000000..771542ad0 --- /dev/null +++ b/src/mips/psyqo/vector.hh @@ -0,0 +1,45 @@ +/* + +MIT License + +Copyright (c) 2023 PCSX-Redux authors + +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 "psyqo/fixed-point.hh" +#include "psyqo/primitives/common.hh" + +namespace psyqo { + +struct Vec2 { + FixedPoint<> x, y; + operator Vertex() const { return {{ .x = x.integer(), .y = y.integer() }}; } +}; + +struct Vec3 { + FixedPoint<> x, y, z; +}; + +struct Vec4 { + FixedPoint<> x, y, z, w; +}; + +} // namespace psyqo diff --git a/src/spu/miniaudio.cc b/src/spu/miniaudio.cc index 55d7c9b64..a7497fe90 100644 --- a/src/spu/miniaudio.cc +++ b/src/spu/miniaudio.cc @@ -162,6 +162,7 @@ void PCSX::SPU::MiniAudio::init(bool safe) { void PCSX::SPU::MiniAudio::uninit() { ma_device_uninit(&m_device); ma_device_uninit(&m_deviceNull); + ma_context_uninit(&m_context); } void PCSX::SPU::MiniAudio::maybeRestart() { diff --git a/src/support/ffmpeg-audio-file.cc b/src/support/ffmpeg-audio-file.cc index 0d5e61228..c1ab4a9f8 100644 --- a/src/support/ffmpeg-audio-file.cc +++ b/src/support/ffmpeg-audio-file.cc @@ -19,8 +19,47 @@ #include "support/ffmpeg-audio-file.h" -PCSX::FFmpegAudioFile::FFmpegAudioFile(IO file, Channels channels, Endianness endianess, unsigned frequency) - : File(RO_SEEKABLE), m_file(file), m_channels(channels), m_endianess(endianess) { +#include + +#include + +AVSampleFormat PCSX::FFmpegAudioFile::getSampleFormat() const { + switch (m_sampleFormat) { + case SampleFormat::U8: + return AV_SAMPLE_FMT_U8; + case SampleFormat::S16: + return AV_SAMPLE_FMT_S16; + case SampleFormat::S32: + return AV_SAMPLE_FMT_S32; + case SampleFormat::F32: + return AV_SAMPLE_FMT_FLT; + case SampleFormat::D64: + return AV_SAMPLE_FMT_DBL; + default: + return AV_SAMPLE_FMT_NONE; + } +} + +unsigned PCSX::FFmpegAudioFile::getSampleSize() const { + switch (m_sampleFormat) { + case SampleFormat::U8: + return 1; + case SampleFormat::S16: + return 2; + case SampleFormat::S32: + return 4; + case SampleFormat::F32: + return 4; + case SampleFormat::D64: + return 8; + default: + return 0; + } +} + +PCSX::FFmpegAudioFile::FFmpegAudioFile(IO file, Channels channels, Endianness endianess, + SampleFormat sampleFormat, unsigned frequency) + : File(RO_SEEKABLE), m_file(file), m_channels(channels), m_endianess(endianess), m_sampleFormat(sampleFormat) { av_log_set_level(AV_LOG_QUIET); unsigned char *buffer = reinterpret_cast(av_malloc(4096)); @@ -73,18 +112,18 @@ PCSX::FFmpegAudioFile::FFmpegAudioFile(IO file, Channels channels, Endiann return; } double duration = ((double)m_formatContext->duration) / ((double)AV_TIME_BASE); - unsigned sampleSize = 2; - if (channels == CHANNELS_STEREO) sampleSize *= 2; + unsigned sampleSize = getSampleSize(); + if (channels == Channels::Stereo) sampleSize *= 2; m_size = ceil(duration * frequency * sampleSize); m_packet = av_packet_alloc(); m_decodedFrame = av_frame_alloc(); m_resampledFrame = av_frame_alloc(); m_resampledFrame->sample_rate = frequency; - m_resampledFrame->format = AV_SAMPLE_FMT_S16; - m_resampledFrame->ch_layout.nb_channels = channels == CHANNELS_STEREO ? 2 : 1; + m_resampledFrame->format = getSampleFormat(); + m_resampledFrame->ch_layout.nb_channels = channels == Channels::Stereo ? 2 : 1; m_resampledFrame->ch_layout.order = AV_CHANNEL_ORDER_NATIVE; - m_resampledFrame->ch_layout.u.mask = channels == CHANNELS_STEREO ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO; + m_resampledFrame->ch_layout.u.mask = channels == Channels::Stereo ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO; m_resampledFrame->nb_samples = 0; const AVCodec *codec; ret = av_find_best_stream(m_formatContext, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); @@ -109,12 +148,12 @@ PCSX::FFmpegAudioFile::FFmpegAudioFile(IO file, Channels channels, Endiann } AVChannelLayout layout; - if (channels == CHANNELS_STEREO) { + if (channels == Channels::Stereo) { layout = AV_CHANNEL_LAYOUT_STEREO; } else { layout = AV_CHANNEL_LAYOUT_MONO; } - if (swr_alloc_set_opts2(&m_resamplerContext, &layout, AV_SAMPLE_FMT_S16, frequency, &m_codecContext->ch_layout, + if (swr_alloc_set_opts2(&m_resamplerContext, &layout, getSampleFormat(), frequency, &m_codecContext->ch_layout, m_codecContext->sample_fmt, m_codecContext->sample_rate, 0, nullptr) < 0) { m_failed = true; return; @@ -192,8 +231,8 @@ ssize_t PCSX::FFmpegAudioFile::read(void *dest_, size_t size) { } ssize_t PCSX::FFmpegAudioFile::decompSome(void *dest_, ssize_t size) { - unsigned sampleSize = 2; - if (m_channels == CHANNELS_STEREO) sampleSize *= 2; + unsigned sampleSize = getSampleSize(); + if (m_channels == Channels::Stereo) sampleSize *= 2; ssize_t dataRead = 0; uint8_t *dest = reinterpret_cast(dest_); ssize_t available = m_resampledFrame->nb_samples * sampleSize - m_packetPtr; @@ -202,6 +241,16 @@ ssize_t PCSX::FFmpegAudioFile::decompSome(void *dest_, ssize_t size) { while (true) { ssize_t toCopy = std::min(available, size); if (toCopy) { + if (((std::endian::native == std::endian::little) && (m_endianess == Endianness::Little)) || + ((std::endian::native == std::endian::big) && (m_endianess == Endianness::Big))) { + memcpy(dest, m_resampledFrame->data[0] + m_packetPtr, toCopy); + } else { + for (ssize_t i = 0; i < toCopy; i += sampleSize) { + for (unsigned j = 0; j < sampleSize; ++j) { + dest[i + j] = m_resampledFrame->data[0][m_packetPtr + i + sampleSize - j - 1]; + } + } + } memcpy(dest, m_resampledFrame->data[0] + m_packetPtr, toCopy); m_packetPtr += toCopy; m_totalOut += toCopy; diff --git a/src/support/ffmpeg-audio-file.h b/src/support/ffmpeg-audio-file.h index 5f254710a..19da250eb 100644 --- a/src/support/ffmpeg-audio-file.h +++ b/src/support/ffmpeg-audio-file.h @@ -33,9 +33,10 @@ namespace PCSX { class FFmpegAudioFile : public File { public: - enum Channels { CHANNELS_STEREO, CHANNELS_MONO }; - enum Endianness { ENDIANNESS_LITTLE, ENDIANNESS_BIG }; - FFmpegAudioFile(IO file, Channels, Endianness, unsigned frequency); + enum class Channels { Stereo, Mono }; + enum class Endianness { Little, Big }; + enum class SampleFormat { U8, S16, S32, F32, D64 }; + FFmpegAudioFile(IO file, Channels, Endianness, SampleFormat, unsigned frequency); virtual ~FFmpegAudioFile() {} virtual ssize_t rSeek(ssize_t pos, int wheel) final override; virtual ssize_t rTell() final override { return m_filePtr; } @@ -45,11 +46,15 @@ class FFmpegAudioFile : public File { throw std::runtime_error("Unable to determine file size"); } virtual bool eof() final override { return m_hitEOF; } - virtual File* dup() final override { return new FFmpegAudioFile(m_file, m_channels, m_endianess, m_frequency); }; + virtual File* dup() final override { + return new FFmpegAudioFile(m_file, m_channels, m_endianess, m_sampleFormat, m_frequency); + }; virtual bool failed() final override { return m_failed || m_file->failed(); } private: virtual void closeInternal() final override; + AVSampleFormat getSampleFormat() const; + unsigned getSampleSize() const; ssize_t decompSome(void* dest, ssize_t size); IO m_file; ssize_t m_filePtr = 0; @@ -58,6 +63,7 @@ class FFmpegAudioFile : public File { bool m_failed = false; Channels m_channels; Endianness m_endianess; + SampleFormat m_sampleFormat; unsigned m_frequency; AVFormatContext* m_formatContext = nullptr; AVIOContext* m_ioContext = nullptr; diff --git a/src/support/file.h b/src/support/file.h index 370f4c2c3..9a67da44c 100644 --- a/src/support/file.h +++ b/src/support/file.h @@ -33,6 +33,7 @@ #include #include +#include "support/polyfills.h" #include "support/slice.h" #include "support/ssize_t.h" @@ -49,27 +50,8 @@ class File; template concept FileDerived = std::is_base_of::value; -template -concept IntegralConcept = std::is_integral::value; - class File { public: -#if defined(__cpp_lib_byteswap) && !defined(_WIN32) - using byte_swap = std::byte_swap; -#else - template - static constexpr T byte_swap(T val) { - if constexpr (sizeof(T) == 1) { - return val; - } else { - T ret = 0; - for (size_t i = 0; i < sizeof(T); i++) { - ret |= static_cast(static_cast(val >> (i * 8)) << ((sizeof(T) - i - 1) * 8)); - } - return ret; - } - } -#endif enum FileType { RO_STREAM, RW_STREAM, RO_SEEKABLE, RW_SEEKABLE }; virtual ~File() { if (m_refCount.load() != 0) { @@ -190,48 +172,48 @@ class File { void writeString(const std::string_view& str) { write(str.data(), str.size()); } void writeStringAt(const std::string_view& str, ssize_t pos) { writeAt(str.data(), str.size(), pos); } - template + template T read() { T ret = T(0); read(&ret, sizeof(T)); if constexpr (endianess != std::endian::native) { - ret = byte_swap(ret); + ret = PolyFill::byteSwap(ret); } return ret; } - template + template T peek() { T ret = T(0); readAt(&ret, sizeof(T), rTell()); if constexpr (endianess != std::endian::native) { - ret = byte_swap(ret); + ret = PolyFill::byteSwap(ret); } return ret; } - template + template T readAt(size_t pos) { T ret = T(0); readAt(&ret, sizeof(T), pos); if constexpr (endianess != std::endian::native) { - ret = byte_swap(ret); + ret = PolyFill::byteSwap(ret); } return ret; } - template + template void write(T val) { if constexpr (endianess != std::endian::native) { - val = byte_swap(val); + val = PolyFill::byteSwap(val); } write(&val, sizeof(T)); } - template + template void writeAt(T val, size_t pos) { if constexpr (endianess != std::endian::native) { - val = byte_swap(val); + val = PolyFill::byteSwap(val); } writeAt(&val, sizeof(T), pos); } @@ -250,7 +232,7 @@ class File { void skip(size_t amount) { rSeek(amount, SEEK_CUR); } - template + template void skip() { skip(sizeof(T)); } diff --git a/src/support/polyfills.h b/src/support/polyfills.h new file mode 100644 index 000000000..f624dba01 --- /dev/null +++ b/src/support/polyfills.h @@ -0,0 +1,53 @@ +/*************************************************************************** + * Copyright (C) 2023 PCSX-Redux authors * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace PCSX { + +namespace PolyFill { + +// MacOS / AppleClang is bad. +template +concept IntegralConcept = std::is_integral::value; + +template +static constexpr T byteSwap(T val) { +#if defined(__cpp_lib_byteswap) && (__cpp_lib_byteswap >= 202110L) + return std::byteswap(val); +#else + if constexpr (sizeof(T) == 1) { + return val; + } else { + T ret = 0; + for (size_t i = 0; i < sizeof(T); i++) { + ret |= static_cast(static_cast(val >> (i * 8)) << ((sizeof(T) - i - 1) * 8)); + } + return ret; + } +#endif +} + +} + +} diff --git a/src/support/settings.h b/src/support/settings.h index 73c2ba58c..d4ea57f21 100644 --- a/src/support/settings.h +++ b/src/support/settings.h @@ -99,7 +99,7 @@ struct Setting, defaultValue> { return *this; } json serialize() const { return value; } - void deserialize(const json &j) { value = j; } + void deserialize(const json &j) { value = j.template get(); } void reset() { value = defaultValue; } type value = defaultValue; }; @@ -151,7 +151,7 @@ struct SettingString, irqus::typestring> { } const char *c_str() const { return value.c_str(); } json serialize() const { return value; } - void deserialize(const json &j) { value = j; } + void deserialize(const json &j) { value = j.template get(); } void reset() { value = defaultValue::data(); } type value = defaultValue::data(); }; @@ -210,7 +210,7 @@ struct SettingPath, irqus::typestring> { // Also, https://github.com/nlohmann/json/issues/1914 json serialize() const { return reinterpret_cast(value.u8string().c_str()); } void deserialize(const json &j) { - std::string str = j; + std::string str = j.template get(); value = str; } void reset() { value = defaultValue::data(); } @@ -263,7 +263,7 @@ struct SettingFloat, defaultValue, divisor> { return *this; } json serialize() const { return value; } - void deserialize(const json &j) { value = j; } + void deserialize(const json &j) { value = j.template get(); } void reset() { value = (float)defaultValue / (float)divisor; } float value = (float)defaultValue / (float)divisor; }; diff --git a/src/support/slice.h b/src/support/slice.h index 615302ec1..db8ed37a9 100644 --- a/src/support/slice.h +++ b/src/support/slice.h @@ -121,9 +121,7 @@ class Slice { } void acquire(std::string &&str) { m_data = std::move(str); } void acquire(void *data, uint32_t size) { - m_data = Owned{size, malloc(size)}; - std::get(m_data).ptr = data; - std::get(m_data).size = size; + m_data = Owned{size, data}; } void borrow(const Slice &other, uint32_t from = 0, uint32_t amount = std::numeric_limits::max()) { const uint8_t *ptr = static_cast(other.data()); diff --git a/src/support/uvfile.cc b/src/support/uvfile.cc index 5ce1c3be9..5e7ac4002 100644 --- a/src/support/uvfile.cc +++ b/src/support/uvfile.cc @@ -110,7 +110,13 @@ void PCSX::UvThreadOp::startThread() { c_tick, c_tick); barrier.set_value(); uv_run(&s_uvLoop, UV_RUN_DEFAULT); - uv_loop_close(&s_uvLoop); + uv_close(reinterpret_cast(&s_kicker), [](auto handle) {}); + uv_close(reinterpret_cast(&s_timer), [](auto handle) {}); + uv_close(reinterpret_cast(&s_curlTimeout), [](auto handle) {}); + while (uv_loop_close(&s_uvLoop) == UV_EBUSY) { + uv_run(&s_uvLoop, UV_RUN_NOWAIT); + } + curl_multi_cleanup(s_curlMulti); }); f.wait(); } @@ -184,8 +190,9 @@ void PCSX::UvThreadOp::stopThread() { request([&barrier](auto loop) { barrier.set_value(); }); barrier.get_future().wait(); request([](auto loop) { - uv_close(reinterpret_cast(&s_kicker), [](auto handle) {}); - uv_close(reinterpret_cast(&s_timer), [](auto handle) {}); + uv_unref(reinterpret_cast(&s_kicker)); + uv_unref(reinterpret_cast(&s_timer)); + uv_unref(reinterpret_cast(&s_curlTimeout)); }); s_uvThread.join(); s_threadRunning = false; @@ -266,7 +273,10 @@ void PCSX::UvFile::openwrapper(const char *filename, int flags) { } m_handle = handle; m_size = size; - if (handle >= 0) m_failed = false; + if (handle >= 0) { + m_failed = false; + m_pendingCloseInfo = new PendingCloseInfo(); + } } PCSX::UvFile::UvFile(const char *filename) : File(RO_SEEKABLE), m_filename(filename) { diff --git a/src/support/uvfile.h b/src/support/uvfile.h index ef31789b8..d61b4177f 100644 --- a/src/support/uvfile.h +++ b/src/support/uvfile.h @@ -218,7 +218,7 @@ class UvFile : public File, public UvThreadOp { bool closePending = false; }; static void closeUVHandle(uv_file handle, uv_loop_s* loop, PendingCloseInfo* pendingCloseInfo); - PendingCloseInfo* m_pendingCloseInfo = new PendingCloseInfo(); + PendingCloseInfo* m_pendingCloseInfo = nullptr; }; class UvFifo : public File, public UvThreadOp { diff --git a/src/support/version.cc b/src/support/version.cc index 6a9a4d297..f691ca7b8 100644 --- a/src/support/version.cc +++ b/src/support/version.cc @@ -32,11 +32,11 @@ void PCSX::VersionInfo::loadFromFile(IO file) { json = nlohmann::json::parse(container.begin(), container.end()); try { - version = json["version"]; - changeset = json["changeset"]; - timestamp = json["timestamp"]; - updateCatalog = json["updateInfo"][0]["updateCatalog"]; - updateInfoBase = json["updateInfo"][0]["updateInfoBase"]; + version = json["version"].template get(); + changeset = json["changeset"].template get(); + timestamp = json["timestamp"].template get(); + updateCatalog = json["updateInfo"][0]["updateCatalog"].template get(); + updateInfoBase = json["updateInfo"][0]["updateInfoBase"].template get(); } catch (...) { clear(); } @@ -63,12 +63,12 @@ bool PCSX::Update::downloadUpdateInfo(const VersionInfo& versionInfo, std::funct std::sort(catalog.begin(), catalog.end(), [](const nlohmann::json& a, const nlohmann::json& b) { return a["id"] > b["id"]; }); auto latest = catalog[0]; - if (latest["version"] == versionInfo.version) { + if (latest["version"].template get() == versionInfo.version) { callback(false); return; } - m_updateId = latest["id"]; - m_updateVersion = latest["version"]; + m_updateId = latest["id"].template get(); + m_updateVersion = latest["version"].template get(); } catch (...) { callback(false); return; @@ -95,7 +95,7 @@ bool PCSX::Update::downloadAndApplyUpdate(const VersionInfo& versionInfo, std::f FileAsContainer container(m_download); nlohmann::json update; update = nlohmann::json::parse(container.begin(), container.end()); - url = update["download_url"]; + url = update["download_url"].template get(); } catch (...) { callback(false); return; @@ -130,7 +130,7 @@ bool PCSX::Update::getDownloadUrl(const VersionInfo& versionInfo, std::function< FileAsContainer container(m_download); nlohmann::json update; update = nlohmann::json::parse(container.begin(), container.end()); - url = update["download_url"]; + url = update["download_url"].template get(); } catch (...) { callback(""); return; diff --git a/src/supportpsx/ps1-packer.cc b/src/supportpsx/ps1-packer.cc index 9c3ed3967..5f096e35b 100644 --- a/src/supportpsx/ps1-packer.cc +++ b/src/supportpsx/ps1-packer.cc @@ -97,26 +97,28 @@ void PCSX::PS1Packer::pack(IO src, IO dest, uint32_t addr, uint32_t pushBytes(stub, lui(Reg::V0, getHI(newPC))); pushBytes(stub, addiu(Reg::V0, Reg::V0, getLO(newPC))); pushBytes(stub, jr(Reg::V0)); - pushBytes(stub, 0); + pushBytes(stub, nop()); std::copy(stub.begin(), stub.end(), dataOut.begin()); + compLoad += stub.size(); } for (auto b : n2e_d::code) pushBytes(dataOut, b); + pushBytes(dataOut, addiu(Reg::T8, Reg::RA, 0)); pushBytes(dataOut, lui(Reg::V1, 0x1f80)); pushBytes(dataOut, sw(Reg::R0, 0x1074, Reg::V1)); pushBytes(dataOut, lui(Reg::A0, getHI(compLoad))); pushBytes(dataOut, addiu(Reg::A0, Reg::A0, getLO(compLoad))); pushBytes(dataOut, lui(Reg::A1, getHI(addr))); - pushBytes(dataOut, bgezal(Reg::R0, -((int16_t)(sizeof(n2e_d::code) + 6 * 4)))); + pushBytes(dataOut, bgezal(Reg::R0, -((int16_t)(sizeof(n2e_d::code) + 7 * 4)))); pushBytes(dataOut, addiu(Reg::A1, Reg::A1, getLO(addr))); if (options.shell) { pushBytes(dataOut, bgezal(Reg::R0, 36)); pushBytes(dataOut, addiu(Reg::S0, Reg::R0, 0xa0)); // this goes to 0x40 - pushBytes(dataOut, 0x40803800); // mtc0 $0, $t7 + pushBytes(dataOut, mtc0(Reg::R0, 7)); pushBytes(dataOut, jr(Reg::RA)); - pushBytes(dataOut, 0x42000010); // rfe + pushBytes(dataOut, rfe()); // this goes to 0x80030000 pushBytes(dataOut, lui(Reg::T0, getHI(pc))); pushBytes(dataOut, addiu(Reg::T0, Reg::T0, getLO(pc))); @@ -146,18 +148,21 @@ void PCSX::PS1Packer::pack(IO src, IO dest, uint32_t addr, uint32_t pushBytes(dataOut, lui(Reg::T0, 0b1100101010000000)); pushBytes(dataOut, lui(Reg::T1, 0x8003)); pushBytes(dataOut, addiu(Reg::T2, Reg::R0, -1)); - pushBytes(dataOut, 0x40883800); // mtc0 $t0, $7 - pushBytes(dataOut, 0x40892800); // mtc0 $t1, $5 - pushBytes(dataOut, 0x408a4800); // mtc0 $t2, $9 + pushBytes(dataOut, mtc0(Reg::R0, 7)); + pushBytes(dataOut, mtc0(Reg::T1, 5)); + pushBytes(dataOut, mtc0(Reg::T2, 9)); + pushBytes(dataOut, mtc0(Reg::T0, 7)); pushBytes(dataOut, jr(Reg::S0)); pushBytes(dataOut, addiu(Reg::T1, Reg::R0, 0x44)); } else { pushBytes(dataOut, addiu(Reg::T0, Reg::R0, 0xa0)); - pushBytes(dataOut, lui(Reg::RA, getHI(pc))); - pushBytes(dataOut, addiu(Reg::RA, Reg::RA, getLO(pc))); - pushBytes(dataOut, jr(Reg::T0)); + pushBytes(dataOut, jalr(Reg::T0)); pushBytes(dataOut, addiu(Reg::T1, Reg::R0, 0x44)); + pushBytes(dataOut, lui(Reg::T0, getHI(pc))); + pushBytes(dataOut, addiu(Reg::T0, Reg::T0, getLO(pc))); + pushBytes(dataOut, jr(Reg::T0)); + pushBytes(dataOut, addiu(Reg::RA, Reg::T8, 0)); } while (!options.cpe && !options.booty && !options.rom && !options.raw && ((dataOut.size() & 0x7ff) != 0)) { dataOut.push_back(0); diff --git a/third_party/EASTL/include/EASTL/bit.h b/third_party/EASTL/include/EASTL/bit.h index 64efe4879..b4d3792ff 100644 --- a/third_party/EASTL/include/EASTL/bit.h +++ b/third_party/EASTL/include/EASTL/bit.h @@ -13,7 +13,6 @@ #include #include -#include // memcpy namespace eastl { @@ -54,7 +53,7 @@ namespace eastl inline To bit_cast(const From& from) EA_NOEXCEPT { typename eastl::aligned_storage::type to; - ::memcpy(eastl::addressof(to), eastl::addressof(from), sizeof(To)); + __builtin_memcpy(eastl::addressof(to), eastl::addressof(from), sizeof(To)); return reinterpret_cast(to); } diff --git a/third_party/EASTL/include/EASTL/bitset.h b/third_party/EASTL/include/EASTL/bitset.h index 8778372f6..90669a505 100644 --- a/third_party/EASTL/include/EASTL/bitset.h +++ b/third_party/EASTL/include/EASTL/bitset.h @@ -817,7 +817,7 @@ namespace eastl if(NW > 16) // This is a constant expression and should be optimized away. { // This will be fastest if compiler intrinsic function optimizations are enabled. - memset(mWord, 0, sizeof(mWord)); + __builtin_memset(mWord, 0, sizeof(mWord)); } else { diff --git a/third_party/EASTL/include/EASTL/deque.h b/third_party/EASTL/include/EASTL/deque.h index c2d55b1c6..73f6aace4 100644 --- a/third_party/EASTL/include/EASTL/deque.h +++ b/third_party/EASTL/include/EASTL/deque.h @@ -615,7 +615,7 @@ namespace eastl EASTL_ASSERT_MSG(p != nullptr, "the behaviour of eastl::allocators that return nullptr is not defined."); #if EASTL_DEBUG - memset((void*)p, 0, kDequeSubarraySize * sizeof(T)); + __builtin_memset((void*)p, 0, kDequeSubarraySize * sizeof(T)); #endif return (T*)p; @@ -648,7 +648,7 @@ namespace eastl EASTL_ASSERT_MSG(pp != nullptr, "the behaviour of eastl::allocators that return nullptr is not defined."); #if EASTL_DEBUG - memset((void*)pp, 0, n * sizeof(T*)); + __builtin_memset((void*)pp, 0, n * sizeof(T*)); #endif return pp; @@ -772,10 +772,10 @@ namespace eastl nAdditionalCapacity = (nUnusedPtrCountAtFront / 2); pPtrArrayBegin = mpPtrArray + (nUnusedPtrCountAtFront - nAdditionalCapacity); - memmove(pPtrArrayBegin, mItBegin.mpCurrentArrayPtr, nUsedPtrSpace); + __builtin_memmove(pPtrArrayBegin, mItBegin.mpCurrentArrayPtr, nUsedPtrSpace); #if EASTL_DEBUG - memset(pPtrArrayBegin + nUsedPtrCount, 0, (size_t)(mpPtrArray + mnPtrArraySize) - (size_t)(pPtrArrayBegin + nUsedPtrCount)); + __builtin_memset(pPtrArrayBegin + nUsedPtrCount, 0, (size_t)(mpPtrArray + mnPtrArraySize) - (size_t)(pPtrArrayBegin + nUsedPtrCount)); #endif } else if((allocationSide == kSideFront) && (nAdditionalCapacity <= nUnusedPtrCountAtBack)) // If we can take advantage of unused pointers at the back without doing any reallocation... @@ -784,10 +784,10 @@ namespace eastl nAdditionalCapacity = (nUnusedPtrCountAtBack / 2); pPtrArrayBegin = mItBegin.mpCurrentArrayPtr + nAdditionalCapacity; - memmove(pPtrArrayBegin, mItBegin.mpCurrentArrayPtr, nUsedPtrSpace); + __builtin_memmove(pPtrArrayBegin, mItBegin.mpCurrentArrayPtr, nUsedPtrSpace); #if EASTL_DEBUG - memset(mpPtrArray, 0, (size_t)((uintptr_t)pPtrArrayBegin - (uintptr_t)mpPtrArray)); + __builtin_memset(mpPtrArray, 0, (size_t)((uintptr_t)pPtrArrayBegin - (uintptr_t)mpPtrArray)); #endif } else @@ -801,7 +801,7 @@ namespace eastl // The following is equivalent to: eastl::copy(mItBegin.mpCurrentArrayPtr, mItEnd.mpCurrentArrayPtr + 1, pPtrArrayBegin); // It's OK to use memcpy instead of memmove because the destination is guaranteed to non-overlap the source. if(mpPtrArray) // Could also say: 'if(mItBegin.mpCurrentArrayPtr)' - memcpy(pPtrArrayBegin, mItBegin.mpCurrentArrayPtr, nUsedPtrSpace); + __builtin_memcpy(pPtrArrayBegin, mItBegin.mpCurrentArrayPtr, nUsedPtrSpace); DoFreePtrArray(mpPtrArray, mnPtrArraySize); @@ -1051,7 +1051,7 @@ namespace eastl // Currently we only do memcpy if the entire operation occurs within a single subarray. if((first.mpBegin == last.mpBegin) && (first.mpBegin == mpBegin)) // If all operations are within the same subarray, implement the operation as a memmove. { - memmove(mpCurrent, first.mpCurrent, (size_t)((uintptr_t)last.mpCurrent - (uintptr_t)first.mpCurrent)); + __builtin_memmove(mpCurrent, first.mpCurrent, (size_t)((uintptr_t)last.mpCurrent - (uintptr_t)first.mpCurrent)); return *this + (last.mpCurrent - first.mpCurrent); } return eastl::copy(eastl::make_move_iterator(first), eastl::make_move_iterator(last), eastl::make_move_iterator(*this)).base(); @@ -1072,7 +1072,7 @@ namespace eastl // To do: Implement this as a loop which does memmoves between subarrays appropriately. // Currently we only do memcpy if the entire operation occurs within a single subarray. if((first.mpBegin == last.mpBegin) && (first.mpBegin == mpBegin)) // If all operations are within the same subarray, implement the operation as a memcpy. - memmove(mpCurrent - (last.mpCurrent - first.mpCurrent), first.mpCurrent, (size_t)((uintptr_t)last.mpCurrent - (uintptr_t)first.mpCurrent)); + __builtin_memmove(mpCurrent - (last.mpCurrent - first.mpCurrent), first.mpCurrent, (size_t)((uintptr_t)last.mpCurrent - (uintptr_t)first.mpCurrent)); else eastl::copy_backward(eastl::make_move_iterator(first), eastl::make_move_iterator(last), eastl::make_move_iterator(*this)); } diff --git a/third_party/EASTL/include/EASTL/internal/config.h b/third_party/EASTL/include/EASTL/internal/config.h index f6d4c1858..c3b155975 100644 --- a/third_party/EASTL/include/EASTL/internal/config.h +++ b/third_party/EASTL/include/EASTL/internal/config.h @@ -345,7 +345,7 @@ namespace eastl // names. See the usage of EASTL_EASTDC_VSNPRINTF in string.h for more info. // #if !defined(EASTL_EASTDC_VSNPRINTF) - #define EASTL_EASTDC_VSNPRINTF 1 + #define EASTL_EASTDC_VSNPRINTF 0 #endif diff --git a/third_party/EASTL/include/EASTL/internal/hashtable.h b/third_party/EASTL/include/EASTL/internal/hashtable.h index a9347b181..03798633a 100644 --- a/third_party/EASTL/include/EASTL/internal/hashtable.h +++ b/third_party/EASTL/include/EASTL/internal/hashtable.h @@ -1659,7 +1659,7 @@ namespace eastl EASTL_CT_ASSERT(kHashtableAllocFlagBuckets == 0x00400000); // Currently we expect this to be so, because the allocator has a copy of this enum. node_type** const pBucketArray = (node_type**)EASTLAllocAlignedFlags(mAllocator, (n + 1) * sizeof(node_type*), EASTL_ALIGN_OF(node_type*), 0, kHashtableAllocFlagBuckets); //eastl::fill(pBucketArray, pBucketArray + n, (node_type*)NULL); - memset(pBucketArray, 0, n * sizeof(node_type*)); + __builtin_memset(pBucketArray, 0, n * sizeof(node_type*)); pBucketArray[n] = reinterpret_cast((uintptr_t)~0); return pBucketArray; } @@ -3014,7 +3014,7 @@ namespace eastl mpBucketArray = (node_type**)&gpEmptyBucketArray[0]; #else void* p = &gpEmptyBucketArray[0]; - memcpy(&mpBucketArray, &p, sizeof(mpBucketArray)); // Other compilers implement strict aliasing and casting is thus unsafe. + __builtin_memcpy(&mpBucketArray, &p, sizeof(mpBucketArray)); // Other compilers implement strict aliasing and casting is thus unsafe. #endif mnElementCount = 0; diff --git a/third_party/EASTL/include/EASTL/internal/intrusive_hashtable.h b/third_party/EASTL/include/EASTL/internal/intrusive_hashtable.h index dccca5b1d..60c7fc4cb 100644 --- a/third_party/EASTL/include/EASTL/internal/intrusive_hashtable.h +++ b/third_party/EASTL/include/EASTL/internal/intrusive_hashtable.h @@ -491,7 +491,7 @@ namespace eastl mHash(h), mEqual(eq) { - memset(mBucketArray, 0, kBucketCount * sizeof(mBucketArray[0])); + __builtin_memset(mBucketArray, 0, kBucketCount * sizeof(mBucketArray[0])); mBucketArray[kBucketCount] = reinterpret_cast((uintptr_t)~0); } @@ -875,7 +875,7 @@ namespace eastl inline void intrusive_hashtable::clear() { // To consider: In debug builds set the node mpNext to NULL. - memset(mBucketArray, 0, kBucketCount * sizeof(mBucketArray[0])); + __builtin_memset(mBucketArray, 0, kBucketCount * sizeof(mBucketArray[0])); mnElementCount = 0; } diff --git a/third_party/EASTL/include/EASTL/memory.h b/third_party/EASTL/include/EASTL/memory.h index c506aa4b7..0015e80a0 100644 --- a/third_party/EASTL/include/EASTL/memory.h +++ b/third_party/EASTL/include/EASTL/memory.h @@ -441,7 +441,7 @@ namespace eastl if (EASTL_UNLIKELY(first == last)) return dest; - return (T*)memcpy(dest, first, (size_t)((uintptr_t)last - (uintptr_t)first)) + (last - first); + return (T*)__builtin_memcpy(dest, first, (size_t)((uintptr_t)last - (uintptr_t)first)) + (last - first); } template diff --git a/third_party/EASTL/include/EASTL/sort.h b/third_party/EASTL/include/EASTL/sort.h index b851871b8..a5470b580 100644 --- a/third_party/EASTL/include/EASTL/sort.h +++ b/third_party/EASTL/include/EASTL/sort.h @@ -1367,7 +1367,7 @@ namespace eastl stack_curr--; #if EASTL_DEV_DEBUG - memset(&run_stack[stack_curr], 0, sizeof(run_stack[stack_curr])); + __builtin_memset(&run_stack[stack_curr], 0, sizeof(run_stack[stack_curr])); #endif break; @@ -1380,7 +1380,7 @@ namespace eastl stack_curr--; #if EASTL_DEV_DEBUG - memset(&run_stack[stack_curr], 0, sizeof(run_stack[stack_curr])); + __builtin_memset(&run_stack[stack_curr], 0, sizeof(run_stack[stack_curr])); #endif break; @@ -1405,7 +1405,7 @@ namespace eastl #if EASTL_DEV_DEBUG EASTL_DEV_ASSERT((run_stack[stack_curr - 2].start + run_stack[stack_curr - 2].length) <= size); EASTL_DEV_ASSERT((run_stack[stack_curr - 1].start + run_stack[stack_curr - 1].length) <= size); - memset(&run_stack[stack_curr], 0, sizeof(run_stack[stack_curr])); + __builtin_memset(&run_stack[stack_curr], 0, sizeof(run_stack[stack_curr])); #endif } else @@ -1417,7 +1417,7 @@ namespace eastl #if EASTL_DEV_DEBUG EASTL_DEV_ASSERT((run_stack[stack_curr - 1].start + run_stack[stack_curr - 1].length) <= size); - memset(&run_stack[stack_curr], 0, sizeof(run_stack[stack_curr])); + __builtin_memset(&run_stack[stack_curr], 0, sizeof(run_stack[stack_curr])); #endif } } @@ -1430,7 +1430,7 @@ namespace eastl #if EASTL_DEV_DEBUG EASTL_DEV_ASSERT((run_stack[stack_curr - 1].start + run_stack[stack_curr - 1].length) <= size); - memset(&run_stack[stack_curr], 0, sizeof(run_stack[stack_curr])); + __builtin_memset(&run_stack[stack_curr], 0, sizeof(run_stack[stack_curr])); #endif } else @@ -1487,7 +1487,7 @@ namespace eastl #if EASTL_DEV_DEBUG EASTL_DEV_ASSERT((run_stack[stack_curr - 1].start + run_stack[stack_curr - 1].length) <= size); - memset(&run_stack[stack_curr], 0, sizeof(run_stack[stack_curr])); + __builtin_memset(&run_stack[stack_curr], 0, sizeof(run_stack[stack_curr])); #endif } @@ -1554,7 +1554,7 @@ namespace eastl const intptr_t minrun = timsort_compute_minrun(size); #if EASTL_DEV_DEBUG - memset(run_stack, 0, sizeof(run_stack)); + __builtin_memset(run_stack, 0, sizeof(run_stack)); #endif if(tim_sort_add_run(run_stack, first, pBuffer, size, minrun, len, run, curr, stack_curr, compare)) @@ -1671,7 +1671,7 @@ namespace eastl { if (doSeparateHistogramCalculation) { - memset(bucketSize, 0, sizeof(bucketSize)); + __builtin_memset(bucketSize, 0, sizeof(bucketSize)); // Calculate histogram for the first scatter operation for (temp = srcFirst; temp != last; ++temp) ++bucketSize[(extractKey(*temp) >> j) & bucketMask]; diff --git a/third_party/EASTL/include/EASTL/string.h b/third_party/EASTL/include/EASTL/string.h index 477954499..84fe1d047 100644 --- a/third_party/EASTL/include/EASTL/string.h +++ b/third_party/EASTL/include/EASTL/string.h @@ -1948,12 +1948,12 @@ namespace eastl const size_type n = (size_type)(pEnd - pBegin); if(n <= internalLayout().GetSize()) { - memmove(internalLayout().BeginPtr(), pBegin, (size_t)n * sizeof(value_type)); + __builtin_memmove(internalLayout().BeginPtr(), pBegin, (size_t)n * sizeof(value_type)); erase(internalLayout().BeginPtr() + n, internalLayout().EndPtr()); } else { - memmove(internalLayout().BeginPtr(), pBegin, (size_t)(internalLayout().GetSize()) * sizeof(value_type)); + __builtin_memmove(internalLayout().BeginPtr(), pBegin, (size_t)(internalLayout().GetSize()) * sizeof(value_type)); append(pBegin + internalLayout().GetSize(), pEnd); } return *this; @@ -2142,7 +2142,7 @@ namespace eastl const size_type nSavedSize = internalLayout().GetSize(); CharStringUninitializedCopy((internalLayout().EndPtr() - n) + 1, internalLayout().EndPtr() + 1, internalLayout().EndPtr() + 1); internalLayout().SetSize(nSavedSize + n); - memmove(const_cast(p) + n, p, (size_t)((nElementsAfter - n) + 1) * sizeof(value_type)); + __builtin_memmove(const_cast(p) + n, p, (size_t)((nElementsAfter - n) + 1) * sizeof(value_type)); CharTypeAssignN(const_cast(p), n, c); } else @@ -2243,8 +2243,8 @@ namespace eastl const size_type nSavedSize = internalLayout().GetSize(); CharStringUninitializedCopy((internalLayout().EndPtr() - n) + 1, internalLayout().EndPtr() + 1, internalLayout().EndPtr() + 1); internalLayout().SetSize(nSavedSize + n); - memmove(const_cast(p) + n, p, (size_t)((nElementsAfter - n) + 1) * sizeof(value_type)); - memmove(const_cast(p), pBegin, (size_t)(n) * sizeof(value_type)); + __builtin_memmove(const_cast(p) + n, p, (size_t)((nElementsAfter - n) + 1) * sizeof(value_type)); + __builtin_memmove(const_cast(p), pBegin, (size_t)(n) * sizeof(value_type)); } else { @@ -2343,7 +2343,7 @@ namespace eastl EASTL_FAIL_MSG("basic_string::erase -- invalid position"); #endif - memmove(const_cast(p), p + 1, (size_t)(internalLayout().EndPtr() - p) * sizeof(value_type)); + __builtin_memmove(const_cast(p), p + 1, (size_t)(internalLayout().EndPtr() - p) * sizeof(value_type)); internalLayout().SetSize(internalLayout().GetSize() - 1); return const_cast(p); } @@ -2543,9 +2543,9 @@ namespace eastl if(nLength1 >= nLength2) // If we have a non-expanding operation... { if((pBegin2 > pEnd1) || (pEnd2 <= pBegin1)) // If we have a non-overlapping operation... - memcpy(const_cast(pBegin1), pBegin2, (size_t)(pEnd2 - pBegin2) * sizeof(value_type)); + __builtin_memcpy(const_cast(pBegin1), pBegin2, (size_t)(pEnd2 - pBegin2) * sizeof(value_type)); else - memmove(const_cast(pBegin1), pBegin2, (size_t)(pEnd2 - pBegin2) * sizeof(value_type)); + __builtin_memmove(const_cast(pBegin1), pBegin2, (size_t)(pEnd2 - pBegin2) * sizeof(value_type)); erase(pBegin1 + nLength2, pEnd1); } else // Else we are expanding. @@ -2555,9 +2555,9 @@ namespace eastl const value_type* const pMid2 = pBegin2 + nLength1; if((pEnd2 <= pBegin1) || (pBegin2 > pEnd1)) - memcpy(const_cast(pBegin1), pBegin2, (size_t)(pMid2 - pBegin2) * sizeof(value_type)); + __builtin_memcpy(const_cast(pBegin1), pBegin2, (size_t)(pMid2 - pBegin2) * sizeof(value_type)); else - memmove(const_cast(pBegin1), pBegin2, (size_t)(pMid2 - pBegin2) * sizeof(value_type)); + __builtin_memmove(const_cast(pBegin1), pBegin2, (size_t)(pMid2 - pBegin2) * sizeof(value_type)); insert(pEnd1, pMid2, pEnd2); } else // else we have an overlapping operation. @@ -3192,7 +3192,7 @@ namespace eastl if((internalLayout().EndPtr() + 1) <= internalLayout().CapacityPtr()) { const size_type nSavedSize = internalLayout().GetSize(); - memmove(const_cast(p) + 1, p, (size_t)(internalLayout().EndPtr() - p) * sizeof(value_type)); + __builtin_memmove(const_cast(p) + 1, p, (size_t)(internalLayout().EndPtr() - p) * sizeof(value_type)); *(internalLayout().EndPtr() + 1) = 0; *pNewPosition = c; internalLayout().SetSize(nSavedSize + 1); @@ -3751,7 +3751,7 @@ namespace eastl template inline bool operator==(const basic_string& a, const basic_string& b) { - return ((a.size() == b.size()) && (memcmp(a.data(), b.data(), (size_t)a.size() * sizeof(typename basic_string::value_type)) == 0)); + return ((a.size() == b.size()) && (__builtin_memcmp(a.data(), b.data(), (size_t)a.size() * sizeof(typename basic_string::value_type)) == 0)); } @@ -3760,7 +3760,7 @@ namespace eastl { typedef typename basic_string::size_type size_type; const size_type n = (size_type)CharStrlen(p); - return ((n == b.size()) && (memcmp(p, b.data(), (size_t)n * sizeof(*p)) == 0)); + return ((n == b.size()) && (__builtin_memcmp(p, b.data(), (size_t)n * sizeof(*p)) == 0)); } @@ -3769,7 +3769,7 @@ namespace eastl { typedef typename basic_string::size_type size_type; const size_type n = (size_type)CharStrlen(p); - return ((a.size() == n) && (memcmp(a.data(), p, (size_t)n * sizeof(*p)) == 0)); + return ((a.size() == n) && (__builtin_memcmp(a.data(), p, (size_t)n * sizeof(*p)) == 0)); } diff --git a/third_party/EASTL/include/EASTL/string_hash_map.h b/third_party/EASTL/include/EASTL/string_hash_map.h index 25bdfaf19..5b49d6ef3 100644 --- a/third_party/EASTL/include/EASTL/string_hash_map.h +++ b/third_party/EASTL/include/EASTL/string_hash_map.h @@ -179,7 +179,7 @@ string_hash_map::strduplicate(const char* str) { size_t len = strlen(str); char* result = (char*)EASTLAlloc(base::base_type::get_allocator(), (len + 1)); - memcpy(result, str, len+1); + __builtin_memcpy(result, str, len+1); return result; } diff --git a/third_party/EASTL/include/EASTL/string_map.h b/third_party/EASTL/include/EASTL/string_map.h index b952e39dc..7f75edcf4 100644 --- a/third_party/EASTL/include/EASTL/string_map.h +++ b/third_party/EASTL/include/EASTL/string_map.h @@ -157,7 +157,7 @@ string_map::strduplicate(const char* str) { size_t len = strlen(str); char* result = (char*)base::base_type::get_allocator().allocate(len + 1); - memcpy(result, str, len+1); + __builtin_memcpy(result, str, len+1); return result; } diff --git a/third_party/EASTL/source/string.cpp b/third_party/EASTL/source/string.cpp index ae73f1140..ad15a109c 100644 --- a/third_party/EASTL/source/string.cpp +++ b/third_party/EASTL/source/string.cpp @@ -288,7 +288,7 @@ namespace eastl if(sourceSize > destSize) sourceSize = destSize; - memmove(pDest, pSrc, sourceSize * sizeof(*pSrcEnd)); + __builtin_memmove(pDest, pSrc, sourceSize * sizeof(*pSrcEnd)); pSrc += sourceSize; pDest += sourceSize; // Intentionally add sourceSize here. @@ -338,7 +338,7 @@ namespace eastl if(sourceSize > destSize) sourceSize = destSize; - memmove(pDest, pSrc, sourceSize * sizeof(*pSrcEnd)); + __builtin_memmove(pDest, pSrc, sourceSize * sizeof(*pSrcEnd)); pSrc += sourceSize; pDest += sourceSize; // Intentionally add sourceSize here. @@ -396,7 +396,7 @@ namespace eastl if(sourceSize > destSize) sourceSize = destSize; - memmove(pDest, pSrc, sourceSize * sizeof(*pSrcEnd)); + __builtin_memmove(pDest, pSrc, sourceSize * sizeof(*pSrcEnd)); pSrc += sourceSize; pDest += sourceSize; // Intentionally add sourceSize here. diff --git a/third_party/cueparser/cueparser.c b/third_party/cueparser/cueparser.c index 621081c05..00228d959 100644 --- a/third_party/cueparser/cueparser.c +++ b/third_party/cueparser/cueparser.c @@ -51,6 +51,7 @@ enum Keyword { KW_CDTEXTFILE = 0xbbd08639, KW_DCP = 0x0b87bad2, KW_FILE = 0x7c7e1383, + KW_FLAC = 0x7c7e292d, KW_FLAGS = 0x0c434e1a, KW_INDEX = 0x0cda305b, KW_ISRC = 0x7c8344ae, @@ -60,6 +61,8 @@ enum Keyword { KW_MODE2_2352 = 0x0e924d5d, KW_MOTOROLA = 0x7ba98cec, KW_MP3 = 0x0b87c98b, + KW_OGG = 0x0b87e14a, + KW_OPUS = 0x7c84409c, KW_PERFORMER = 0xcc7e14e3, KW_POSTGAP = 0xb61fc6ab, KW_PRE = 0x0b880de2, @@ -96,20 +99,20 @@ static void end_closure_call(struct CueClosure* closure_) { closure->parser->cb(closure->parser, closure->scheduler, closure->error); } -static void reset_word(char* word) { *(uint8_t*)&word[255] = 255; } +static void reset_word(char* word) { *(uint8_t*)&word[255] = 0; } static int append_to_word(char* word, int c) { uint8_t* b = (uint8_t*)word; - int len = 255 - b[255]; + int len = b[255]; if (len == 255) return 0; word[len++] = c; - b[255] = 255 - len; + b[255] = len; return 1; } static int word_len(char* word) { uint8_t* b = (uint8_t*)word; - return 255 - b[255]; + return b[255]; } static void end_word(char* word) { @@ -156,8 +159,6 @@ void CueParser_close(struct CueParser* parser, struct CueScheduler* scheduler, parser->currentFile->user = parser; parser->currentFile->close(parser->currentFile, scheduler, close_cb); } - } else { - parser->currentFile = NULL; } } @@ -232,7 +233,10 @@ static void parse(struct CueParser* parser, struct CueFile* file, struct CueSche continue; } if (parser->inRem) { - if (isEOL) parser->inRem = 0; + if (isEOL) { + parser->inRem = 0; + new_keyword(parser); + } continue; } if (parser->afterQuotes) { @@ -403,9 +407,25 @@ static void parse(struct CueParser* parser, struct CueFile* file, struct CueSche parser->currentFile->size(parser->currentFile, scheduler, 1, size_cb); return; break; + case KW_OGG: + parser->currentFileType = CUE_FILE_TYPE_OGG; + parser->currentFile->size(parser->currentFile, scheduler, 1, size_cb); + return; + break; + case KW_OPUS: + parser->currentFileType = CUE_FILE_TYPE_OPUS; + parser->currentFile->size(parser->currentFile, scheduler, 1, size_cb); + return; + break; + case KW_FLAC: + parser->currentFileType = CUE_FILE_TYPE_FLAC; + parser->currentFile->size(parser->currentFile, scheduler, 1, size_cb); + return; + break; default: end_parse(parser, scheduler, "cuesheet unknown FILE filetype"); return; + break; } break; case CUE_PARSER_FLAGS: { @@ -649,7 +669,13 @@ static void parse(struct CueParser* parser, struct CueFile* file, struct CueSche schedule_read(parser, file, scheduler); } -static void parse_eof(struct CueParser* parser, struct CueScheduler* scheduler) { +static void parse_eof(struct CueParser* parser, struct CueFile* file, struct CueScheduler* scheduler) { + if (parser->state != CUE_PARSER_START) { + parser->amount = 1; + parser->start = "\n"; + parse(parser, file, scheduler); + return; + } if (parser->currentFile) { if (parser->currentFile->references == 1) { end_parse(parser, scheduler, "cuesheet has too many FILE without TRACK"); @@ -684,7 +710,7 @@ static void read_bytes(struct CueFile* file, struct CueScheduler* scheduler, int uint8_t* buffer) { struct CueParser* parser = file->user; if (amount == 0) { - parse_eof(parser, scheduler); + parse_eof(parser, file, scheduler); } else { parser->amount = amount; parser->cursor += amount; diff --git a/third_party/cueparser/cueparser.h b/third_party/cueparser/cueparser.h index 8bf28121e..39dd3e23a 100644 --- a/third_party/cueparser/cueparser.h +++ b/third_party/cueparser/cueparser.h @@ -61,6 +61,9 @@ enum CueFileType { CUE_FILE_TYPE_AIFF, CUE_FILE_TYPE_WAVE, CUE_FILE_TYPE_MP3, + CUE_FILE_TYPE_OGG, + CUE_FILE_TYPE_OPUS, + CUE_FILE_TYPE_FLAC, }; struct CueParser { diff --git a/third_party/flags.h b/third_party/flags.h index 97a95bf1f..fea59ab88 100644 --- a/third_party/flags.h +++ b/third_party/flags.h @@ -91,14 +91,8 @@ inline std::optional get_value( inline std::vector get_values( const argument_map& options, const std::string_view& option) { std::vector values; - if (options.bucket_count() == 0) return values; - argument_map::size_type bucket = options.bucket(option); - values.reserve(options.bucket_size(bucket)); - auto iter = options.cbegin(bucket); - auto end = options.cend(bucket); - while (iter != end) { - auto & opt = *iter++; - if (opt.second.has_value()) { + for (auto & opt : options) { + if ((opt.first == option) && (opt.second.has_value())) { values.emplace_back(opt.second.value()); } } diff --git a/third_party/imgui_lua_bindings/generate_imgui_bindings.pl b/third_party/imgui_lua_bindings/generate_imgui_bindings.pl old mode 100644 new mode 100755 index 9daf288a0..f3a349e85 --- a/third_party/imgui_lua_bindings/generate_imgui_bindings.pl +++ b/third_party/imgui_lua_bindings/generate_imgui_bindings.pl @@ -250,8 +250,8 @@ sub generateImguiGeneric { # const char* a or const char* a = NULL or "blah" } elsif ($args[$i] =~ m/^ *const char\* *([^ =\[]*)( *= *(NULL|".*")|) *$/) { my $name = $1; - if ($2 =~ m/^ *= *NULL$/) { - push(@before, "OPTIONAL_LABEL_ARG($name)"); + if ($2 =~ m/^ *= *([^ ]*)$/) { + push(@before, "OPTIONAL_LABEL_ARG($name, $1)"); } else { push(@before, "LABEL_ARG($name)"); } diff --git a/third_party/imgui_lua_bindings/imgui_iterator.inl b/third_party/imgui_lua_bindings/imgui_iterator.inl index 8a87afc94..25e856159 100644 --- a/third_party/imgui_lua_bindings/imgui_iterator.inl +++ b/third_party/imgui_lua_bindings/imgui_iterator.inl @@ -632,7 +632,7 @@ END_IMGUI_FUNC // IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // raw text without formatting. Roughly equivalent to Text("%s", text) but: A) doesn't require null terminated string if 'text_end' is specified, B) it's faster, no memory copy is done, no buffer size limits, recommended for long chunks of text. IMGUI_FUNCTION(TextUnformatted) LABEL_ARG(text) -OPTIONAL_LABEL_ARG(text_end) +OPTIONAL_LABEL_ARG(text_end, NULL) CALL_FUNCTION_NO_RET(TextUnformatted, text, text_end) END_IMGUI_FUNC // IMGUI_API void Text(const char* fmt, ...) IM_FMTARGS(1); // formatted text @@ -738,7 +738,7 @@ END_IMGUI_FUNC IMGUI_FUNCTION(ProgressBar) NUMBER_ARG(fraction) OPTIONAL_IM_VEC_2_ARG(size_arg, -FLT_MIN, 0) -OPTIONAL_LABEL_ARG(overlay) +OPTIONAL_LABEL_ARG(overlay, NULL) CALL_FUNCTION_NO_RET(ProgressBar, fraction, size_arg, overlay) END_IMGUI_FUNC // IMGUI_API void Bullet(); // draw a small circle + keep the cursor on the same line. advance cursor x position by GetTreeNodeToLabelSpacing(), same distance that TreeNode() uses @@ -804,7 +804,7 @@ FLOAT_POINTER_ARG(v) OPTIONAL_NUMBER_ARG(v_speed, 1.0f) OPTIONAL_NUMBER_ARG(v_min, 0.0f) OPTIONAL_NUMBER_ARG(v_max, 0.0f) -LABEL_ARG(format) +OPTIONAL_LABEL_ARG(format, "%.3f") OPTIONAL_INT_ARG(flags, 0) CALL_FUNCTION(DragFloat, bool, label, v, v_speed, v_min, v_max, format, flags) PUSH_BOOL(ret) @@ -824,8 +824,8 @@ FLOAT_POINTER_ARG(v_current_max) OPTIONAL_NUMBER_ARG(v_speed, 1.0f) OPTIONAL_NUMBER_ARG(v_min, 0.0f) OPTIONAL_NUMBER_ARG(v_max, 0.0f) -LABEL_ARG(format) -OPTIONAL_LABEL_ARG(format_max) +OPTIONAL_LABEL_ARG(format, "%.3f") +OPTIONAL_LABEL_ARG(format_max, NULL) OPTIONAL_INT_ARG(flags, 0) CALL_FUNCTION(DragFloatRange2, bool, label, v_current_min, v_current_max, v_speed, v_min, v_max, format, format_max, flags) PUSH_BOOL(ret) @@ -839,7 +839,7 @@ INT_POINTER_ARG(v) OPTIONAL_NUMBER_ARG(v_speed, 1.0f) OPTIONAL_INT_ARG(v_min, 0) OPTIONAL_INT_ARG(v_max, 0) -LABEL_ARG(format) +OPTIONAL_LABEL_ARG(format, "%d") OPTIONAL_INT_ARG(flags, 0) CALL_FUNCTION(DragInt, bool, label, v, v_speed, v_min, v_max, format, flags) PUSH_BOOL(ret) @@ -859,8 +859,8 @@ INT_POINTER_ARG(v_current_max) OPTIONAL_NUMBER_ARG(v_speed, 1.0f) OPTIONAL_INT_ARG(v_min, 0) OPTIONAL_INT_ARG(v_max, 0) -LABEL_ARG(format) -OPTIONAL_LABEL_ARG(format_max) +OPTIONAL_LABEL_ARG(format, "%d") +OPTIONAL_LABEL_ARG(format_max, NULL) OPTIONAL_INT_ARG(flags, 0) CALL_FUNCTION(DragIntRange2, bool, label, v_current_min, v_current_max, v_speed, v_min, v_max, format, format_max, flags) PUSH_BOOL(ret) @@ -881,7 +881,7 @@ LABEL_ARG(label) FLOAT_POINTER_ARG(v) NUMBER_ARG(v_min) NUMBER_ARG(v_max) -LABEL_ARG(format) +OPTIONAL_LABEL_ARG(format, "%.3f") OPTIONAL_INT_ARG(flags, 0) CALL_FUNCTION(SliderFloat, bool, label, v, v_min, v_max, format, flags) PUSH_BOOL(ret) @@ -911,7 +911,7 @@ LABEL_ARG(label) INT_POINTER_ARG(v) INT_ARG(v_min) INT_ARG(v_max) -LABEL_ARG(format) +OPTIONAL_LABEL_ARG(format, "%d") OPTIONAL_INT_ARG(flags, 0) CALL_FUNCTION(SliderInt, bool, label, v, v_min, v_max, format, flags) PUSH_BOOL(ret) @@ -938,7 +938,7 @@ IM_VEC_2_ARG(size) FLOAT_POINTER_ARG(v) NUMBER_ARG(v_min) NUMBER_ARG(v_max) -LABEL_ARG(format) +OPTIONAL_LABEL_ARG(format, "%.3f") OPTIONAL_INT_ARG(flags, 0) CALL_FUNCTION(VSliderFloat, bool, label, size, v, v_min, v_max, format, flags) PUSH_BOOL(ret) @@ -951,7 +951,7 @@ IM_VEC_2_ARG(size) INT_POINTER_ARG(v) INT_ARG(v_min) INT_ARG(v_max) -LABEL_ARG(format) +OPTIONAL_LABEL_ARG(format, "%d") OPTIONAL_INT_ARG(flags, 0) CALL_FUNCTION(VSliderInt, bool, label, size, v, v_min, v_max, format, flags) PUSH_BOOL(ret) @@ -982,7 +982,7 @@ LABEL_ARG(label) FLOAT_POINTER_ARG(v) OPTIONAL_NUMBER_ARG(step, 0.0f) OPTIONAL_NUMBER_ARG(step_fast, 0.0f) -LABEL_ARG(format) +OPTIONAL_LABEL_ARG(format, "%.3f") OPTIONAL_INT_ARG(flags, 0) CALL_FUNCTION(InputFloat, bool, label, v, step, step_fast, format, flags) PUSH_BOOL(ret) @@ -1163,7 +1163,7 @@ IMGUI_FUNCTION(PlotLines) LABEL_ARG(label) FLOAT_ARRAY_ARG(values) OPTIONAL_INT_ARG(values_offset, 0) -OPTIONAL_LABEL_ARG(overlay_text) +OPTIONAL_LABEL_ARG(overlay_text, NULL) OPTIONAL_NUMBER_ARG(scale_min, FLT_MAX) OPTIONAL_NUMBER_ARG(scale_max, FLT_MAX) OPTIONAL_IM_VEC_2_ARG(graph_size, 0, 0) @@ -1179,7 +1179,7 @@ IMGUI_FUNCTION(PlotHistogram) LABEL_ARG(label) FLOAT_ARRAY_ARG(values) OPTIONAL_INT_ARG(values_offset, 0) -OPTIONAL_LABEL_ARG(overlay_text) +OPTIONAL_LABEL_ARG(overlay_text, NULL) OPTIONAL_NUMBER_ARG(scale_min, FLT_MAX) OPTIONAL_NUMBER_ARG(scale_max, FLT_MAX) OPTIONAL_IM_VEC_2_ARG(graph_size, 0, 0) @@ -1212,7 +1212,7 @@ END_IMGUI_FUNC IMGUI_FUNCTION(Value_3) LABEL_ARG(prefix) NUMBER_ARG(v) -OPTIONAL_LABEL_ARG(float_format) +OPTIONAL_LABEL_ARG(float_format, NULL) CALL_FUNCTION_NO_RET(Value, prefix, v, float_format) END_IMGUI_FUNC // IMGUI_API bool BeginMenuBar(); // append to menu-bar of current window (requires ImGuiWindowFlags_MenuBar flag set on parent window). @@ -1253,7 +1253,7 @@ END_IMGUI_FUNC // IMGUI_API bool MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true); // return true when activated. IMGUI_FUNCTION(MenuItem) LABEL_ARG(label) -OPTIONAL_LABEL_ARG(shortcut) +OPTIONAL_LABEL_ARG(shortcut, NULL) OPTIONAL_BOOL_ARG(selected, false) OPTIONAL_BOOL_ARG(enabled, true) CALL_FUNCTION(MenuItem, bool, label, shortcut, selected, enabled) @@ -1321,7 +1321,7 @@ CALL_FUNCTION_NO_RET(OpenPopup, id, popup_flags) END_IMGUI_FUNC // IMGUI_API void OpenPopupOnItemClick(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // helper to open popup when clicked on last item. Default to ImGuiPopupFlags_MouseButtonRight == 1. (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors) IMGUI_FUNCTION(OpenPopupOnItemClick) -OPTIONAL_LABEL_ARG(str_id) +OPTIONAL_LABEL_ARG(str_id, NULL) OPTIONAL_INT_ARG(popup_flags, 1) CALL_FUNCTION_NO_RET(OpenPopupOnItemClick, str_id, popup_flags) END_IMGUI_FUNC @@ -1331,7 +1331,7 @@ CALL_FUNCTION_NO_RET(CloseCurrentPopup) END_IMGUI_FUNC // IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // open+begin popup when clicked on last item. Use str_id==NULL to associate the popup to previous item. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp! IMGUI_FUNCTION(BeginPopupContextItem) -OPTIONAL_LABEL_ARG(str_id) +OPTIONAL_LABEL_ARG(str_id, NULL) OPTIONAL_INT_ARG(popup_flags, 1) CALL_FUNCTION(BeginPopupContextItem, bool, str_id, popup_flags) IF_RET_ADD_END_STACK(12) @@ -1339,7 +1339,7 @@ PUSH_BOOL(ret) END_IMGUI_FUNC // IMGUI_API bool BeginPopupContextWindow(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1);// open+begin popup when clicked on current window. IMGUI_FUNCTION(BeginPopupContextWindow) -OPTIONAL_LABEL_ARG(str_id) +OPTIONAL_LABEL_ARG(str_id, NULL) OPTIONAL_INT_ARG(popup_flags, 1) CALL_FUNCTION(BeginPopupContextWindow, bool, str_id, popup_flags) IF_RET_ADD_END_STACK(12) @@ -1347,7 +1347,7 @@ PUSH_BOOL(ret) END_IMGUI_FUNC // IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // open+begin popup when clicked in void (where there are no windows). IMGUI_FUNCTION(BeginPopupContextVoid) -OPTIONAL_LABEL_ARG(str_id) +OPTIONAL_LABEL_ARG(str_id, NULL) OPTIONAL_INT_ARG(popup_flags, 1) CALL_FUNCTION(BeginPopupContextVoid, bool, str_id, popup_flags) IF_RET_ADD_END_STACK(12) @@ -1448,7 +1448,7 @@ END_IMGUI_FUNC // IMGUI_API void Columns(int count = 1, const char* id = NULL, bool border = true); IMGUI_FUNCTION(Columns) OPTIONAL_INT_ARG(count, 1) -OPTIONAL_LABEL_ARG(id) +OPTIONAL_LABEL_ARG(id, NULL) OPTIONAL_BOOL_ARG(border, true) CALL_FUNCTION_NO_RET(Columns, count, id, border) END_IMGUI_FUNC @@ -1555,7 +1555,7 @@ END_IMGUI_FUNC // IMGUI_API void LogToFile(int auto_open_depth = -1, const char* filename = NULL); // start logging to file IMGUI_FUNCTION(LogToFile) OPTIONAL_INT_ARG(auto_open_depth, -1) -OPTIONAL_LABEL_ARG(filename) +OPTIONAL_LABEL_ARG(filename, NULL) CALL_FUNCTION_NO_RET(LogToFile, auto_open_depth, filename) END_IMGUI_FUNC // IMGUI_API void LogToClipboard(int auto_open_depth = -1); // start logging to OS clipboard @@ -1788,7 +1788,7 @@ END_IMGUI_FUNC // IMGUI_API ImVec2 CalcTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); IMGUI_FUNCTION(CalcTextSize) LABEL_ARG(text) -OPTIONAL_LABEL_ARG(text_end) +OPTIONAL_LABEL_ARG(text_end, NULL) OPTIONAL_BOOL_ARG(hide_text_after_double_hash, false) OPTIONAL_NUMBER_ARG(wrap_width, -1.0f) CALL_FUNCTION(CalcTextSize, ImVec2, text, text_end, hide_text_after_double_hash, wrap_width) @@ -3102,7 +3102,7 @@ IMGUI_FUNCTION_DRAW_LIST(AddText) IM_VEC_2_ARG(pos) UINT_ARG(col) LABEL_ARG(text_begin) -OPTIONAL_LABEL_ARG(text_end) +OPTIONAL_LABEL_ARG(text_end, NULL) DRAW_LIST_CALL_FUNCTION_NO_RET(AddText, pos, col, text_begin, text_end) END_IMGUI_FUNC // IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); diff --git a/third_party/imgui_lua_bindings/imgui_lua_bindings.cpp b/third_party/imgui_lua_bindings/imgui_lua_bindings.cpp index 911b017ce..115ba3066 100644 --- a/third_party/imgui_lua_bindings/imgui_lua_bindings.cpp +++ b/third_party/imgui_lua_bindings/imgui_lua_bindings.cpp @@ -40,12 +40,12 @@ static int impl_##name(lua_State *L) { \ #define IM_TEXTURE_ID_ARG(name) \ const ImTextureID name = (ImTextureID)luaL_checkinteger(L, arg++); -#define OPTIONAL_LABEL_ARG(name) \ +#define OPTIONAL_LABEL_ARG(name, otherwise) \ const char* name; \ if (arg <= max_args) { \ name = lua_tostring(L, arg++); \ } else { \ - name = NULL; \ + name = otherwise; \ } #define LABEL_ARG(name) \ @@ -264,7 +264,7 @@ static const struct luaL_Reg imguilib[] = { #undef IM_TEXTURE_ID_ARG #define IM_TEXTURE_ID_ARG(name) #undef OPTIONAL_LABEL_ARG - #define OPTIONAL_LABEL_ARG(name) + #define OPTIONAL_LABEL_ARG(name, otherwise) #undef LABEL_ARG #define LABEL_ARG(name) #undef IM_VEC_2_ARG @@ -381,7 +381,7 @@ static void PushImguiEnums(lua_State* lState, const char* tableName) { #undef IM_TEXTURE_ID_ARG #define IM_TEXTURE_ID_ARG(name) #undef OPTIONAL_LABEL_ARG -#define OPTIONAL_LABEL_ARG(name) +#define OPTIONAL_LABEL_ARG(name, otherwise) #undef LABEL_ARG #define LABEL_ARG(name) #undef IM_VEC_2_ARG diff --git a/third_party/lpeg/HISTORY b/third_party/lpeg/HISTORY new file mode 100644 index 000000000..a25070981 --- /dev/null +++ b/third_party/lpeg/HISTORY @@ -0,0 +1,109 @@ +HISTORY for LPeg 1.1.0 + +* Changes from version 1.0.2 to 1.1.0 + --------------------------------- + + accumulator capture + + UTF-8 ranges + + Larger limit for number of rules in a grammar + + Larger limit for number of captures in a match + + bug fixes + + other small improvements + +* Changes from version 1.0.1 to 1.0.2 + --------------------------------- + + some bugs fixed + +* Changes from version 0.12 to 1.0.1 + --------------------------------- + + group "names" can be any Lua value + + some bugs fixed + + other small improvements + +* Changes from version 0.11 to 0.12 + --------------------------------- + + no "unsigned short" limit for pattern sizes + + mathtime captures considered nullable + + some bugs fixed + +* Changes from version 0.10 to 0.11 + ------------------------------- + + complete reimplementation of the code generator + + new syntax for table captures + + new functions in module 're' + + other small improvements + +* Changes from version 0.9 to 0.10 + ------------------------------- + + backtrack stack has configurable size + + better error messages + + Notation for non-terminals in 're' back to A instead o + + experimental look-behind pattern + + support for external extensions + + works with Lua 5.2 + + consumes less C stack + + - "and" predicates do not keep captures + +* Changes from version 0.8 to 0.9 + ------------------------------- + + The accumulator capture was replaced by a fold capture; + programs that used the old 'lpeg.Ca' will need small changes. + + Some support for character classes from old C locales. + + A new named-group capture. + +* Changes from version 0.7 to 0.8 + ------------------------------- + + New "match-time" capture. + + New "argument capture" that allows passing arguments into the pattern. + + Better documentation for 're'. + + Several small improvements for 're'. + + The 're' module has an incompatibility with previous versions: + now, any use of a non-terminal must be enclosed in angle brackets + (like ). + +* Changes from version 0.6 to 0.7 + ------------------------------- + + Several improvements in module 're': + - better documentation; + - support for most captures (all but accumulator); + - limited repetitions p{n,m}. + + Small improvements in efficiency. + + Several small bugs corrected (special thanks to Hans Hagen + and Taco Hoekwater). + +* Changes from version 0.5 to 0.6 + ------------------------------- + + Support for non-numeric indices in grammars. + + Some bug fixes (thanks to the luatex team). + + Some new optimizations; (thanks to Mike Pall). + + A new page layout (thanks to Andre Carregal). + + Minimal documentation for module 're'. + +* Changes from version 0.4 to 0.5 + ------------------------------- + + Several optimizations. + + lpeg.P now accepts booleans. + + Some new examples. + + A proper license. + + Several small improvements. + +* Changes from version 0.3 to 0.4 + ------------------------------- + + Static check for loops in repetitions and grammars. + + Removed label option in captures. + + The implementation of captures uses less memory. + +* Changes from version 0.2 to 0.3 + ------------------------------- + + User-defined patterns in Lua. + + Several new captures. + +* Changes from version 0.1 to 0.2 + ------------------------------- + + Several small corrections. + + Handles embedded zeros like any other character. + + Capture "name" can be any Lua value. + + Unlimited number of captures. + + Match gets an optional initial position. + +(end of HISTORY) diff --git a/third_party/lpeg/README.md b/third_party/lpeg/README.md new file mode 100644 index 000000000..65ac1eb5a --- /dev/null +++ b/third_party/lpeg/README.md @@ -0,0 +1,4 @@ +# LPeg - Parsing Expression Grammars For Lua + +For more information, +see [Lpeg](//www.inf.puc-rio.br/~roberto/lpeg/). diff --git a/third_party/lpeg/lpcap.c b/third_party/lpeg/lpcap.c new file mode 100644 index 000000000..f13ecf4d8 --- /dev/null +++ b/third_party/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/third_party/lpeg/lpcap.h b/third_party/lpeg/lpcap.h new file mode 100644 index 000000000..abbd55371 --- /dev/null +++ b/third_party/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/third_party/lpeg/lpcode.c b/third_party/lpeg/lpcode.c new file mode 100644 index 000000000..f3b8ae365 --- /dev/null +++ b/third_party/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/third_party/lpeg/lpcode.h b/third_party/lpeg/lpcode.h new file mode 100644 index 000000000..10c2ced5b --- /dev/null +++ b/third_party/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/third_party/lpeg/lpcset.c b/third_party/lpeg/lpcset.c new file mode 100644 index 000000000..2dcffd9a4 --- /dev/null +++ b/third_party/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/third_party/lpeg/lpcset.h b/third_party/lpeg/lpcset.h new file mode 100644 index 000000000..b69fef900 --- /dev/null +++ b/third_party/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/third_party/lpeg/lpeg-128.gif b/third_party/lpeg/lpeg-128.gif new file mode 100644 index 000000000..bbf5e78b8 Binary files /dev/null and b/third_party/lpeg/lpeg-128.gif differ diff --git a/third_party/lpeg/lpeg.html b/third_party/lpeg/lpeg.html new file mode 100644 index 000000000..b31d57599 --- /dev/null +++ b/third_party/lpeg/lpeg.html @@ -0,0 +1,1430 @@ + + + + LPeg - Parsing Expression Grammars For Lua + + + + + + +

+ +
+ +
LPeg
+
+ Parsing Expression Grammars For Lua, version 1.1 +
+
+ +
+ + + +
+ + +

Introduction

+ +

+LPeg is a new pattern-matching library for Lua, +based on + +Parsing Expression Grammars (PEGs). +This text is a reference manual for the library. +For a more formal treatment of LPeg, +as well as some discussion about its implementation, +see + +A Text Pattern-Matching Tool based on Parsing Expression Grammars. +(You may also be interested in my +talk about LPeg +given at the III Lua Workshop.) +

+ +

+Following the Snobol tradition, +LPeg defines patterns as first-class objects. +That is, patterns are regular Lua values +(represented by userdata). +The library offers several functions to create +and compose patterns. +With the use of metamethods, +several of these functions are provided as infix or prefix +operators. +On the one hand, +the result is usually much more verbose than the typical +encoding of patterns using the so called +regular expressions +(which typically are not regular expressions in the formal sense). +On the other hand, +first-class patterns allow much better documentation +(as it is easy to comment the code, +to break complex definitions in smaller parts, etc.) +and are extensible, +as we can define new functions to create and compose patterns. +

+ +

+For a quick glance of the library, +the following table summarizes its basic operations +for creating patterns: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OperatorDescription
lpeg.P(string)Matches string literally
lpeg.P(n)Matches exactly n characters
lpeg.S(string)Matches any character in string (Set)
lpeg.R("xy")Matches any character between x and y (Range)
lpeg.utfR(cp1, cp2)Matches an UTF-8 code point between cp1 and + cp2
patt^nMatches at least n repetitions of patt
patt^-nMatches at most n repetitions of patt
patt1 * patt2Matches patt1 followed by patt2
patt1 + patt2Matches patt1 or patt2 + (ordered choice)
patt1 - patt2Matches patt1 if patt2 does not match
-pattEquivalent to ("" - patt)
#pattMatches patt but consumes no input
lpeg.B(patt)Matches patt behind the current position, + consuming no input
+ +

As a very simple example, +lpeg.R("09")^1 creates a pattern that +matches a non-empty sequence of digits. +As a not so simple example, +-lpeg.P(1) +(which can be written as lpeg.P(-1), +or simply -1 for operations expecting a pattern) +matches an empty string only if it cannot match a single character; +so, it succeeds only at the end of the subject. +

+ +

+LPeg also offers the re module, +which implements patterns following a regular-expression style +(e.g., [09]+). +(This module is 270 lines of Lua code, +and of course it uses LPeg to parse regular expressions and +translate them to regular LPeg patterns.) +

+ + +

Functions

+ + +

lpeg.match (pattern, subject [, init])

+

+The matching function. +It attempts to match the given pattern against the subject string. +If the match succeeds, +returns the index in the subject of the first character after the match, +or the captured values +(if the pattern captured any value). +

+ +

+An optional numeric argument init makes the match +start at that position in the subject string. +As in the Lua standard libraries, +a negative value counts from the end. +

+ +

+Unlike typical pattern-matching functions, +match works only in anchored mode; +that is, it tries to match the pattern with a prefix of +the given subject string (at position init), +not with an arbitrary substring of the subject. +So, if we want to find a pattern anywhere in a string, +we must either write a loop in Lua or write a pattern that +matches anywhere. +This second approach is easy and quite efficient; +see examples. +

+ +

lpeg.type (value)

+

+If the given value is a pattern, +returns the string "pattern". +Otherwise returns nil. +

+ +

lpeg.version

+

+A string (not a function) with the running version of LPeg. +

+ +

lpeg.setmaxstack (max)

+

+Sets a limit for the size of the backtrack stack used by LPeg to +track calls and choices. +(The default limit is 400.) +Most well-written patterns need little backtrack levels and +therefore you seldom need to change this limit; +before changing it you should try to rewrite your +pattern to avoid the need for extra space. +Nevertheless, a few useful patterns may overflow. +Also, with recursive grammars, +subjects with deep recursion may also need larger limits. +

+ + +

Basic Constructions

+ +

+The following operations build patterns. +All operations that expect a pattern as an argument +may receive also strings, tables, numbers, booleans, or functions, +which are translated to patterns according to +the rules of function lpeg.P. +

+ + + +

lpeg.P (value)

+

+Converts the given value into a proper pattern, +according to the following rules: +

+
    + +
  • +If the argument is a pattern, +it is returned unmodified. +

  • + +
  • +If the argument is a string, +it is translated to a pattern that matches the string literally. +

  • + +
  • +If the argument is a non-negative number n, +the result is a pattern that matches exactly n characters. +

  • + +
  • +If the argument is a negative number -n, +the result is a pattern that +succeeds only if the input string has less than n characters left: +lpeg.P(-n) +is equivalent to -lpeg.P(n) +(see the unary minus operation). +

  • + +
  • +If the argument is a boolean, +the result is a pattern that always succeeds or always fails +(according to the boolean value), +without consuming any input. +

  • + +
  • +If the argument is a table, +it is interpreted as a grammar +(see Grammars). +

  • + +
  • +If the argument is a function, +returns a pattern equivalent to a +match-time capture over the empty string. +

  • + +
+ + +

lpeg.B(patt)

+

+Returns a pattern that +matches only if the input string at the current position +is preceded by patt. +Pattern patt must match only strings +with some fixed length, +and it cannot contain captures. +

+ +

+Like the and predicate, +this pattern never consumes any input, +independently of success or failure. +

+ + +

lpeg.R ({range})

+

+Returns a pattern that matches any single character +belonging to one of the given ranges. +Each range is a string xy of length 2, +representing all characters with code +between the codes of x and y +(both inclusive). +

+ +

+As an example, the pattern +lpeg.R("09") matches any digit, +and lpeg.R("az", "AZ") matches any ASCII letter. +

+ + +

lpeg.S (string)

+

+Returns a pattern that matches any single character that +appears in the given string. +(The S stands for Set.) +

+ +

+As an example, the pattern +lpeg.S("+-*/") matches any arithmetic operator. +

+ +

+Note that, if s is a character +(that is, a string of length 1), +then lpeg.P(s) is equivalent to lpeg.S(s) +which is equivalent to lpeg.R(s..s). +Note also that both lpeg.S("") and lpeg.R() +are patterns that always fail. +

+ + +

lpeg.utfR (cp1, cp2)

+

+Returns a pattern that matches a valid UTF-8 byte sequence +representing a code point in the range [cp1, cp2]. +The range is limited by the natural Unicode limit of 0x10FFFF, +but may include surrogates. +

+ + +

lpeg.V (v)

+

+This operation creates a non-terminal (a variable) +for a grammar. +The created non-terminal refers to the rule indexed by v +in the enclosing grammar. +(See Grammars for details.) +

+ + +

lpeg.locale ([table])

+

+Returns a table with patterns for matching some character classes +according to the current locale. +The table has fields named +alnum, +alpha, +cntrl, +digit, +graph, +lower, +print, +punct, +space, +upper, and +xdigit, +each one containing a correspondent pattern. +Each pattern matches any single character that belongs to its class. +

+ +

+If called with an argument table, +then it creates those fields inside the given table and +returns that table. +

+ + +

#patt

+

+Returns a pattern that +matches only if the input string matches patt, +but without consuming any input, +independently of success or failure. +(This pattern is called an and predicate +and it is equivalent to +&patt in the original PEG notation.) +

+ + +

+This pattern never produces any capture. +

+ + +

-patt

+

+Returns a pattern that +matches only if the input string does not match patt. +It does not consume any input, +independently of success or failure. +(This pattern is equivalent to +!patt in the original PEG notation.) +

+ +

+As an example, the pattern +-lpeg.P(1) matches only the end of string. +

+ +

+This pattern never produces any captures, +because either patt fails +or -patt fails. +(A failing pattern never produces captures.) +

+ + +

patt1 + patt2

+

+Returns a pattern equivalent to an ordered choice +of patt1 and patt2. +(This is denoted by patt1 / patt2 in the original PEG notation, +not to be confused with the / operation in LPeg.) +It matches either patt1 or patt2, +with no backtracking once one of them succeeds. +The identity element for this operation is the pattern +lpeg.P(false), +which always fails. +

+ +

+If both patt1 and patt2 are +character sets, +this operation is equivalent to set union. +

+
+lower = lpeg.R("az")
+upper = lpeg.R("AZ")
+letter = lower + upper
+
+ + +

patt1 - patt2

+

+Returns a pattern equivalent to !patt2 patt1 +in the origial PEG notation. +This pattern asserts that the input does not match +patt2 and then matches patt1. +

+ +

+When successful, +this pattern produces all captures from patt1. +It never produces any capture from patt2 +(as either patt2 fails or +patt1 - patt2 fails). +

+ +

+If both patt1 and patt2 are +character sets, +this operation is equivalent to set difference. +Note that -patt is equivalent to "" - patt +(or 0 - patt). +If patt is a character set, +1 - patt is its complement. +

+ + +

patt1 * patt2

+

+Returns a pattern that matches patt1 +and then matches patt2, +starting where patt1 finished. +The identity element for this operation is the +pattern lpeg.P(true), +which always succeeds. +

+ +

+(LPeg uses the * operator +[instead of the more obvious ..] +both because it has +the right priority and because in formal languages it is +common to use a dot for denoting concatenation.) +

+ + +

patt^n

+

+If n is nonnegative, +this pattern is +equivalent to pattn patt*: +It matches n or more occurrences of patt. +

+ +

+Otherwise, when n is negative, +this pattern is equivalent to (patt?)-n: +It matches at most |n| +occurrences of patt. +

+ +

+In particular, patt^0 is equivalent to patt*, +patt^1 is equivalent to patt+, +and patt^-1 is equivalent to patt? +in the original PEG notation. +

+ +

+In all cases, +the resulting pattern is greedy with no backtracking +(also called a possessive repetition). +That is, it matches only the longest possible sequence +of matches for patt. +

+ + + +

Grammars

+ +

+With the use of Lua variables, +it is possible to define patterns incrementally, +with each new pattern using previously defined ones. +However, this technique does not allow the definition of +recursive patterns. +For recursive patterns, +we need real grammars. +

+ +

+LPeg represents grammars with tables, +where each entry is a rule. +

+ +

+The call lpeg.V(v) +creates a pattern that represents the nonterminal +(or variable) with index v in a grammar. +Because the grammar still does not exist when +this function is evaluated, +the result is an open reference to the respective rule. +

+ +

+A table is fixed when it is converted to a pattern +(either by calling lpeg.P or by using it wherein a +pattern is expected). +Then every open reference created by lpeg.V(v) +is corrected to refer to the rule indexed by v in the table. +

+ +

+When a table is fixed, +the result is a pattern that matches its initial rule. +The entry with index 1 in the table defines its initial rule. +If that entry is a string, +it is assumed to be the name of the initial rule. +Otherwise, LPeg assumes that the entry 1 itself is the initial rule. +

+ +

+As an example, +the following grammar matches strings of a's and b's that +have the same number of a's and b's: +

+
+equalcount = lpeg.P{
+  "S";   -- initial rule name
+  S = "a" * lpeg.V"B" + "b" * lpeg.V"A" + "",
+  A = "a" * lpeg.V"S" + "b" * lpeg.V"A" * lpeg.V"A",
+  B = "b" * lpeg.V"S" + "a" * lpeg.V"B" * lpeg.V"B",
+} * -1
+
+

+It is equivalent to the following grammar in standard PEG notation: +

+
+  S <- 'a' B / 'b' A / ''
+  A <- 'a' S / 'b' A A
+  B <- 'b' S / 'a' B B
+
+ + +

Captures

+ +

+A capture is a pattern that produces values +(the so called semantic information) +according to what it matches. +LPeg offers several kinds of captures, +which produces values based on matches and combine these values to +produce new values. +Each capture may produce zero or more values. +

+ +

+The following table summarizes the basic captures: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OperationWhat it Produces
lpeg.C(patt)the match for patt plus all captures + made by patt
lpeg.Carg(n)the value of the nth extra argument to + lpeg.match (matches the empty string)
lpeg.Cb(key)the values produced by the previous + group capture named key + (matches the empty string)
lpeg.Cc(values)the given values (matches the empty string)
lpeg.Cf(patt, func)folding capture (deprecated)
lpeg.Cg(patt [, key])the values produced by patt, + optionally tagged with key
lpeg.Cp()the current position (matches the empty string)
lpeg.Cs(patt)the match for patt + with the values from nested captures replacing their matches
lpeg.Ct(patt)a table with all captures from patt
patt / stringstring, with some marks replaced by captures + of patt
patt / numberthe n-th value captured by patt, +or no value when number is zero.
patt / tabletable[c], where c is the (first) + capture of patt
patt / functionthe returns of function applied to the captures + of patt
patt % functionproduces no value; + it accummulates the captures from patt + into the previous capture through function +
lpeg.Cmt(patt, function)the returns of function applied to the captures + of patt; the application is done at match time
+ +

+A capture pattern produces its values only when it succeeds. +For instance, +the pattern lpeg.C(lpeg.P"a"^-1) +produces the empty string when there is no "a" +(because the pattern "a"? succeeds), +while the pattern lpeg.C("a")^-1 +does not produce any value when there is no "a" +(because the pattern "a" fails). +A pattern inside a loop or inside a recursive structure +produces values for each match. +

+ +

+Usually, +LPeg does not specify when (and if) it evaluates its captures. +(As an example, +consider the pattern lpeg.P"a" / func / 0. +Because the "division" by 0 instructs LPeg to throw away the +results from the pattern, +it is not specified whether LPeg will call func.) +Therefore, captures should avoid side effects. +Moreover, +captures cannot affect the way a pattern matches a subject. +The only exception to this rule is the +so-called match-time capture. +When a match-time capture matches, +it forces the immediate evaluation of all its nested captures +and then calls its corresponding function, +which defines whether the match succeeds and also +what values are produced. +

+ +

lpeg.C (patt)

+

+Creates a simple capture, +which captures the substring of the subject that matches patt. +The captured value is a string. +If patt has other captures, +their values are returned after this one. +

+ + +

lpeg.Carg (n)

+

+Creates an argument capture. +This pattern matches the empty string and +produces the value given as the nth extra +argument given in the call to lpeg.match. +

+ + +

lpeg.Cb (key)

+

+Creates a back capture. +This pattern matches the empty string and +produces the values produced by the most recent +group capture named key +(where key can be any Lua value). +

+ +

+Most recent means the last +complete +outermost +group capture with the given key. +A Complete capture means that the entire pattern +corresponding to the capture has matched; +in other words, the back capture is not nested inside the group. +An Outermost capture means that the capture is not inside +another complete capture that does not contain the back capture itself. +

+ +

+In the same way that LPeg does not specify when it evaluates captures, +it does not specify whether it reuses +values previously produced by the group +or re-evaluates them. +

+ +

lpeg.Cc ([value, ...])

+

+Creates a constant capture. +This pattern matches the empty string and +produces all given values as its captured values. +

+ + +

lpeg.Cf (patt, func)

+

+Creates a fold capture. +This construction is deprecated; +use an accumulator pattern instead. +In general, a fold like +lpeg.Cf(p1 * p2^0, func) +can be translated to +(p1 * (p2 % func)^0). + + +

lpeg.Cg (patt [, key])

+

+Creates a group capture. +It groups all values returned by patt +into a single capture. +The group may be anonymous (if no key is given) +or named with the given key +(which can be any non-nil Lua value). +

+ +

+An anonymous group serves to join values from several captures into +a single capture. +A named group has a different behavior. +In most situations, a named group returns no values at all. +Its values are only relevant for a following +back capture or when used +inside a table capture. +

+ + +

lpeg.Cp ()

+

+Creates a position capture. +It matches the empty string and +captures the position in the subject where the match occurs. +The captured value is a number. +

+ + +

lpeg.Cs (patt)

+

+Creates a substitution capture, +which captures the substring of the subject that matches patt, +with substitutions. +For any capture inside patt with a value, +the substring that matched the capture is replaced by the capture value +(which should be a string). +The final captured value is the string resulting from +all replacements. +

+ + +

lpeg.Ct (patt)

+

+Creates a table capture. +This capture returns a table with all values from all anonymous captures +made by patt inside this table in successive integer keys, +starting at 1. +Moreover, +for each named capture group created by patt, +the first value of the group is put into the table +with the group key as its key. +The captured value is only the table. +

+ + +

patt / string

+

+Creates a string capture. +It creates a capture string based on string. +The captured value is a copy of string, +except that the character % works as an escape character: +any sequence in string of the form %n, +with n between 1 and 9, +stands for the match of the n-th capture in patt. +The sequence %0 stands for the whole match. +The sequence %% stands for a single %. +

+ + +

patt / number

+

+Creates a numbered capture. +For a non-zero number, +the captured value is the n-th value +captured by patt. +When number is zero, +there are no captured values. +

+ + +

patt / table

+

+Creates a query capture. +It indexes the given table using as key the first value captured by +patt, +or the whole match if patt produced no value. +The value at that index is the final value of the capture. +If the table does not have that key, +there is no captured value. +

+ + +

patt / function

+

+Creates a function capture. +It calls the given function passing all captures made by +patt as arguments, +or the whole match if patt made no capture. +The values returned by the function +are the final values of the capture. +In particular, +if function returns no value, +there is no captured value. +

+ + +

patt % function

+

+Creates an accumulator capture. +This pattern behaves similarly to a +function capture, +with the following differences: +The last captured value before patt +is added as a first argument to the call; +the return of the function is adjusted to one single value; +that value replaces the last captured value. +Note that the capture itself produces no values; +it only changes the value of its previous capture. +

+ +

+As an example, +let us consider the problem of adding a list of numbers. +

+
+-- matches a numeral and captures its numerical value
+number = lpeg.R"09"^1 / tonumber
+
+-- auxiliary function to add two numbers
+function add (acc, newvalue) return acc + newvalue end
+
+-- matches a list of numbers, adding their values
+sum = number * ("," * number % add)^0
+
+-- example of use
+print(sum:match("10,30,43"))   --> 83
+
+

+First, the initial number captures a number; +that first capture will play the role of an accumulator. +Then, each time the sequence comma-number +matches inside the loop there is an accumulator capture: +It calls add with the current value of the +accumulator—which is the last captured value, created by the +first number— and the value of the new number, +and the result of the call (the sum of the two numbers) +replaces the value of the accumulator. +At the end of the match, +the accumulator with all sums is the final value. +

+ +

+As another example, +consider the following code fragment: +

+
+local name = lpeg.C(lpeg.R("az")^1)
+local p = name * (lpeg.P("^") % string.upper)^-1
+print(p:match("count"))    --> count
+print(p:match("count^"))   --> COUNT
+
+

+In the match against "count", +as there is no "^", +the optional accumulator capture does not match; +so, the match results in its sole capture, a name. +In the match against "count^", +the accumulator capture matches, +so the function string.upper +is called with the previous captured value (created by name) +plus the string "^"; +the function ignores its second argument and returns the first argument +changed to upper case; +that value then becomes the first and only +capture value created by the match. +

+ +

+Due to the nature of this capture, +you should avoid using it in places where it is not clear +what is the "previous" capture, +such as directly nested in a string capture +or a numbered capture. +(Note that these captures may not need to evaluate +all their subcaptures to compute their results.) +Moreover, due to implementation details, +you should not use this capture directly nested in a +substitution capture. +You should also avoid a direct nesting of this capture inside +a folding capture (deprecated), +as the folding will try to fold each individual accumulator capture. +A simple and effective way to avoid all these issues is +to enclose the whole accumulation composition +(including the capture that generates the initial value) +into an anonymous group capture. +

+ + +

lpeg.Cmt(patt, function)

+

+Creates a match-time capture. +Unlike all other captures, +this one is evaluated immediately when a match occurs +(even if it is part of a larger pattern that fails later). +It forces the immediate evaluation of all its nested captures +and then calls function. +

+ +

+The given function gets as arguments the entire subject, +the current position (after the match of patt), +plus any capture values produced by patt. +

+ +

+The first value returned by function +defines how the match happens. +If the call returns a number, +the match succeeds +and the returned number becomes the new current position. +(Assuming a subject s and current position i, +the returned number must be in the range [i, len(s) + 1].) +If the call returns true, +the match succeeds without consuming any input. +(So, to return true is equivalent to return i.) +If the call returns false, nil, or no value, +the match fails. +

+ +

+Any extra values returned by the function become the +values produced by the capture. +

+ + + + +

Some Examples

+ +

Using a Pattern

+

+This example shows a very simple but complete program +that builds and uses a pattern: +

+
+local lpeg = require "lpeg"
+
+-- matches a word followed by end-of-string
+p = lpeg.R"az"^1 * -1
+
+print(p:match("hello"))        --> 6
+print(lpeg.match(p, "hello"))  --> 6
+print(p:match("1 hello"))      --> nil
+
+

+The pattern is simply a sequence of one or more lower-case letters +followed by the end of string (-1). +The program calls match both as a method +and as a function. +In both sucessful cases, +the match returns +the index of the first character after the match, +which is the string length plus one. +

+ + +

Name-value lists

+

+This example parses a list of name-value pairs and returns a table +with those pairs: +

+
+lpeg.locale(lpeg)   -- adds locale entries into 'lpeg' table
+
+local space = lpeg.space^0
+local name = lpeg.C(lpeg.alpha^1) * space
+local sep = lpeg.S(",;") * space
+local pair = name * "=" * space * name * sep^-1
+local list = lpeg.Ct("") * (pair % rawset)^0
+t = list:match("a=b, c = hi; next = pi")
+        --> { a = "b", c = "hi", next = "pi" }
+
+

+Each pair has the format name = name followed by +an optional separator (a comma or a semicolon). +The list pattern then folds these captures. +It starts with an empty table, +created by a table capture matching an empty string; +then for each a pair of names it applies rawset +over the accumulator (the table) and the capture values (the pair of names). +rawset returns the table itself, +so the accumulator is always the table. +

+ +

Splitting a string

+

+The following code builds a pattern that +splits a string using a given pattern +sep as a separator: +

+
+function split (s, sep)
+  sep = lpeg.P(sep)
+  local elem = lpeg.C((1 - sep)^0)
+  local p = elem * (sep * elem)^0
+  return lpeg.match(p, s)
+end
+
+

+First the function ensures that sep is a proper pattern. +The pattern elem is a repetition of zero of more +arbitrary characters as long as there is not a match against +the separator. +It also captures its match. +The pattern p matches a list of elements separated +by sep. +

+ +

+If the split results in too many values, +it may overflow the maximum number of values +that can be returned by a Lua function. +To avoid this problem, +we can collect these values in a table: +

+
+function split (s, sep)
+  sep = lpeg.P(sep)
+  local elem = lpeg.C((1 - sep)^0)
+  local p = lpeg.Ct(elem * (sep * elem)^0)   -- make a table capture
+  return lpeg.match(p, s)
+end
+
+ + +

Searching for a pattern

+

+The primitive match works only in anchored mode. +If we want to find a pattern anywhere in a string, +we must write a pattern that matches anywhere. +

+ +

+Because patterns are composable, +we can write a function that, +given any arbitrary pattern p, +returns a new pattern that searches for p +anywhere in a string. +There are several ways to do the search. +One way is like this: +

+
+function anywhere (p)
+  return lpeg.P{ p + 1 * lpeg.V(1) }
+end
+
+

+This grammar has a straight reading: +its sole rule matches p or skips one character and tries again. +

+ +

+If we want to know where the pattern is in the string +(instead of knowing only that it is there somewhere), +we can add position captures to the pattern: +

+
+local Cp = lpeg.Cp()
+function anywhere (p)
+  return lpeg.P{ Cp * p * Cp + 1 * lpeg.V(1) }
+end
+
+print(anywhere("world"):match("hello world!"))   --> 7   12
+
+ +

+Another option for the search is like this: +

+
+local Cp = lpeg.Cp()
+function anywhere (p)
+  return (1 - lpeg.P(p))^0 * Cp * p * Cp
+end
+
+

+Again the pattern has a straight reading: +it skips as many characters as possible while not matching p, +and then matches p plus appropriate captures. +

+ +

+If we want to look for a pattern only at word boundaries, +we can use the following transformer: +

+ +
+local t = lpeg.locale()
+
+function atwordboundary (p)
+  return lpeg.P{
+    [1] = p + t.alpha^0 * (1 - t.alpha)^1 * lpeg.V(1)
+  }
+end
+
+ + +

Balanced parentheses

+

+The following pattern matches only strings with balanced parentheses: +

+
+b = lpeg.P{ "(" * ((1 - lpeg.S"()") + lpeg.V(1))^0 * ")" }
+
+

+Reading the first (and only) rule of the given grammar, +we have that a balanced string is +an open parenthesis, +followed by zero or more repetitions of either +a non-parenthesis character or +a balanced string (lpeg.V(1)), +followed by a closing parenthesis. +

+ + +

Global substitution

+

+The next example does a job somewhat similar to string.gsub. +It receives a pattern and a replacement value, +and substitutes the replacement value for all occurrences of the pattern +in a given string: +

+
+function gsub (s, patt, repl)
+  patt = lpeg.P(patt)
+  patt = lpeg.Cs((patt / repl + 1)^0)
+  return lpeg.match(patt, s)
+end
+
+

+As in string.gsub, +the replacement value can be a string, +a function, or a table. +

+ + +

Comma-Separated Values (CSV)

+

+This example breaks a string into comma-separated values, +returning all fields: +

+
+local field = '"' * lpeg.Cs(((lpeg.P(1) - '"') + lpeg.P'""' / '"')^0) * '"' +
+                    lpeg.C((1 - lpeg.S',\n"')^0)
+
+local record = field * (',' * field)^0 * (lpeg.P'\n' + -1)
+
+function csv (s)
+  return lpeg.match(record, s)
+end
+
+

+A field is either a quoted field +(which may contain any character except an individual quote, +which may be written as two quotes that are replaced by one) +or an unquoted field +(which cannot contain commas, newlines, or quotes). +A record is a list of fields separated by commas, +ending with a newline or the string end (-1). +

+ +

+As it is, +the previous pattern returns each field as a separated result. +If we add a table capture in the definition of record, +the pattern will return instead a single table +containing all fields: +

+
+local record = lpeg.Ct(field * (',' * field)^0) * (lpeg.P'\n' + -1)
+
+ + +

Lua's long strings

+

+A long string in Lua starts with the pattern [=*[ +and ends at the first occurrence of ]=*] with +exactly the same number of equal signs. +If the opening brackets are followed by a newline, +this newline is discarded +(that is, it is not part of the string). +

+ +

+To match a long string in Lua, +the pattern must capture the first repetition of equal signs and then, +whenever it finds a candidate for closing the string, +check whether it has the same number of equal signs. +

+ +
+equals = lpeg.P"="^0
+open = "[" * lpeg.Cg(equals, "init") * "[" * lpeg.P"\n"^-1
+close = "]" * lpeg.C(equals) * "]"
+closeeq = lpeg.Cmt(close * lpeg.Cb("init"), function (s, i, a, b) return a == b end)
+string = open * lpeg.C((lpeg.P(1) - closeeq)^0) * close / 1
+
+ +

+The open pattern matches [=*[, +capturing the repetitions of equal signs in a group named init; +it also discharges an optional newline, if present. +The close pattern matches ]=*], +also capturing the repetitions of equal signs. +The closeeq pattern first matches close; +then it uses a back capture to recover the capture made +by the previous open, +which is named init; +finally it uses a match-time capture to check +whether both captures are equal. +The string pattern starts with an open, +then it goes as far as possible until matching closeeq, +and then matches the final close. +The final numbered capture simply discards +the capture made by close. +

+ + +

Arithmetic expressions

+

+This example is a complete parser and evaluator for simple +arithmetic expressions. +We write it in two styles. +The first approach first builds a syntax tree and then +traverses this tree to compute the expression value: +

+
+-- Lexical Elements
+local Space = lpeg.S(" \n\t")^0
+local Number = lpeg.C(lpeg.P"-"^-1 * lpeg.R("09")^1) * Space
+local TermOp = lpeg.C(lpeg.S("+-")) * Space
+local FactorOp = lpeg.C(lpeg.S("*/")) * Space
+local Open = "(" * Space
+local Close = ")" * Space
+
+-- Grammar
+local Exp, Term, Factor = lpeg.V"Exp", lpeg.V"Term", lpeg.V"Factor"
+G = lpeg.P{ Exp,
+  Exp = lpeg.Ct(Term * (TermOp * Term)^0);
+  Term = lpeg.Ct(Factor * (FactorOp * Factor)^0);
+  Factor = Number + Open * Exp * Close;
+}
+
+G = Space * G * -1
+
+-- Evaluator
+function eval (x)
+  if type(x) == "string" then
+    return tonumber(x)
+  else
+    local op1 = eval(x[1])
+    for i = 2, #x, 2 do
+      local op = x[i]
+      local op2 = eval(x[i + 1])
+      if (op == "+") then op1 = op1 + op2
+      elseif (op == "-") then op1 = op1 - op2
+      elseif (op == "*") then op1 = op1 * op2
+      elseif (op == "/") then op1 = op1 / op2
+      end
+    end
+    return op1
+  end
+end
+
+-- Parser/Evaluator
+function evalExp (s)
+  local t = lpeg.match(G, s)
+  if not t then error("syntax error", 2) end
+  return eval(t)
+end
+
+-- small example
+print(evalExp"3 + 5*9 / (1+1) - 12")   --> 13.5
+
+ +

+The second style computes the expression value on the fly, +without building the syntax tree. +The following grammar takes this approach. +(It assumes the same lexical elements as before.) +

+
+-- Auxiliary function
+function eval (v1, op, v2)
+  if (op == "+") then return v1 + v2
+  elseif (op == "-") then return v1 - v2
+  elseif (op == "*") then return v1 * v2
+  elseif (op == "/") then return v1 / v2
+  end
+end
+
+-- Grammar
+local V = lpeg.V
+G = lpeg.P{ "Exp",
+  Exp = V"Term" * (TermOp * V"Term" % eval)^0;
+  Term = V"Factor" * (FactorOp * V"Factor" % eval)^0;
+  Factor = Number / tonumber + Open * V"Exp" * Close;
+}
+
+-- small example
+print(lpeg.match(G, "3 + 5*9 / (1+1) - 12"))   --> 13.5
+
+

+Note the use of the accumulator capture. +To compute the value of an expression, +the accumulator starts with the value of the first term, +and then applies eval over +the accumulator, the operator, +and the new term for each repetition. +

+ + + +

Download

+ +

LPeg +source code.

+ +

+Probably, the easiest way to install LPeg is with +LuaRocks. +If you have LuaRocks installed, +the following command is all you need to install LPeg: +

$ luarocks install lpeg
+ + +

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/third_party/lpeg/lpprint.c b/third_party/lpeg/lpprint.c new file mode 100644 index 000000000..da902e6f7 --- /dev/null +++ b/third_party/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/third_party/lpeg/lpprint.h b/third_party/lpeg/lpprint.h new file mode 100644 index 000000000..e8e04e872 --- /dev/null +++ b/third_party/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/third_party/lpeg/lptree.c b/third_party/lpeg/lptree.c new file mode 100644 index 000000000..475b0c36b --- /dev/null +++ b/third_party/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/third_party/lpeg/lptree.h b/third_party/lpeg/lptree.h new file mode 100644 index 000000000..c7887412b --- /dev/null +++ b/third_party/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/third_party/lpeg/lptypes.h b/third_party/lpeg/lptypes.h new file mode 100644 index 000000000..3f860b973 --- /dev/null +++ b/third_party/lpeg/lptypes.h @@ -0,0 +1,150 @@ +/* +** LPeg - PEG pattern matching for Lua +** Copyright 2007-2023, Lua.org & PUC-Rio (see 'lpeg.html' 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/third_party/lpeg/lpvm.c b/third_party/lpeg/lpvm.c new file mode 100644 index 000000000..0a2fde4f5 --- /dev/null +++ b/third_party/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/third_party/lpeg/lpvm.h b/third_party/lpeg/lpvm.h new file mode 100644 index 000000000..684f0c9a0 --- /dev/null +++ b/third_party/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/third_party/lpeg/makefile b/third_party/lpeg/makefile new file mode 100644 index 000000000..41a29280e --- /dev/null +++ b/third_party/lpeg/makefile @@ -0,0 +1,59 @@ +LIBNAME = lpeg +LUADIR = ./lua/ + +COPT = -O2 -DNDEBUG +# COPT = -O0 -DLPEG_DEBUG -g + +CWARNS = -Wall -Wextra -pedantic \ + -Waggregate-return \ + -Wcast-align \ + -Wcast-qual \ + -Wdisabled-optimization \ + -Wpointer-arith \ + -Wshadow \ + -Wredundant-decls \ + -Wsign-compare \ + -Wundef \ + -Wwrite-strings \ + -Wbad-function-cast \ + -Wdeclaration-after-statement \ + -Wmissing-prototypes \ + -Wmissing-declarations \ + -Wnested-externs \ + -Wstrict-prototypes \ + -Wc++-compat \ +# -Wunreachable-code \ + + +CFLAGS = $(CWARNS) $(COPT) -std=c99 -I$(LUADIR) -fPIC +CC = gcc + +FILES = lpvm.o lpcap.o lptree.o lpcode.o lpprint.o lpcset.o + +# For Linux +linux: + $(MAKE) lpeg.so "DLLFLAGS = -shared -fPIC" + +# For Mac OS +macosx: + $(MAKE) lpeg.so "DLLFLAGS = -bundle -undefined dynamic_lookup" + +lpeg.so: $(FILES) + env $(CC) $(DLLFLAGS) $(FILES) -o lpeg.so + +$(FILES): makefile + +test: test.lua re.lua lpeg.so + ./test.lua + +clean: + rm -f $(FILES) lpeg.so + + +lpcap.o: lpcap.c lpcap.h lptypes.h +lpcode.o: lpcode.c lptypes.h lpcode.h lptree.h lpvm.h lpcap.h lpcset.h +lpcset.o: lpcset.c lptypes.h lpcset.h lpcode.h lptree.h lpvm.h lpcap.h +lpprint.o: lpprint.c lptypes.h lpprint.h lptree.h lpvm.h lpcap.h lpcode.h +lptree.o: lptree.c lptypes.h lpcap.h lpcode.h lptree.h lpvm.h lpprint.h \ + lpcset.h +lpvm.o: lpvm.c lpcap.h lptypes.h lpvm.h lpprint.h lptree.h diff --git a/third_party/lpeg/re.html b/third_party/lpeg/re.html new file mode 100644 index 000000000..c8d1bc8db --- /dev/null +++ b/third_party/lpeg/re.html @@ -0,0 +1,472 @@ + + + + LPeg.re - Regex syntax for LPEG + + + + + + +
+ +
+ +
LPeg.re
+
+ Regex syntax for LPEG +
+
+ +
+ + + +
+ +

The re Module

+ +

+The re module +(provided by file re.lua in the distribution) +supports a somewhat conventional regex syntax +for pattern usage within LPeg. +

+ +

+The next table summarizes re's syntax. +A p represents an arbitrary pattern; +num represents a number ([0-9]+); +name represents an identifier +([a-zA-Z][a-zA-Z0-9_]*). +Constructions are listed in order of decreasing precedence. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SyntaxDescription
( p ) grouping
& p and predicate
! p not predicate
p1 p2 concatenation
p1 / p2 ordered choice
p ? optional match
p * zero or more repetitions
p + one or more repetitions
p^numexactly num repetitions
p^+numat least num repetitions
p^-numat most num repetitions
(name <- p)+ grammar
'string' literal string
"string" literal string
[class] character class
. any character
%namepattern defs[name] or a pre-defined pattern
namenon terminal
<name>non terminal
{} position capture
{ p } simple capture
{: p :} anonymous group capture
{:name: p :} named group capture
{~ p ~} substitution capture
{| p |} table capture
=name back reference
p -> 'string' string capture
p -> "string" string capture
p -> num numbered capture
p -> name function/query/string capture +equivalent to p / defs[name]
p => name match-time capture +equivalent to lpeg.Cmt(p, defs[name])
p ~> name fold capture +(deprecated)
p >> name accumulator capture +equivalent to (p % defs[name])
+

+Any space appearing in a syntax description can be +replaced by zero or more space characters and Lua-style short comments +(-- until end of line). +

+ +

+Character classes define sets of characters. +An initial ^ complements the resulting set. +A range x-y includes in the set +all characters with codes between the codes of x and y. +A pre-defined class %name includes all +characters of that class. +A simple character includes itself in the set. +The only special characters inside a class are ^ +(special only if it is the first character); +] +(can be included in the set as the first character, +after the optional ^); +% (special only if followed by a letter); +and - +(can be included in the set as the first or the last character). +

+ +

+Currently the pre-defined classes are similar to those from the +Lua's string library +(%a for letters, +%A for non letters, etc.). +There is also a class %nl +containing only the newline character, +which is particularly handy for grammars written inside long strings, +as long strings do not interpret escape sequences like \n. +

+ + +

Functions

+ +

re.compile (string, [, defs])

+

+Compiles the given string and +returns an equivalent LPeg pattern. +The given string may define either an expression or a grammar. +The optional defs table provides extra Lua values +to be used by the pattern. +

+ +

re.find (subject, pattern [, init])

+

+Searches the given pattern in the given subject. +If it finds a match, +returns the index where this occurrence starts and +the index where it ends. +Otherwise, returns nil. +

+ +

+An optional numeric argument init makes the search +starts at that position in the subject string. +As usual in Lua libraries, +a negative value counts from the end. +

+ +

re.gsub (subject, pattern, replacement)

+

+Does a global substitution, +replacing all occurrences of pattern +in the given subject by replacement. + +

re.match (subject, pattern)

+

+Matches the given pattern against the given subject, +returning all captures. +

+ +

re.updatelocale ()

+

+Updates the pre-defined character classes to the current locale. +

+ + +

Some Examples

+ +

A complete simple program

+

+The next code shows a simple complete Lua program using +the re module: +

+
+local re = require"re"
+
+-- find the position of the first numeral in a string
+print(re.find("the number 423 is odd", "[0-9]+"))  --> 12    14
+
+-- returns all words in a string
+print(re.match("the number 423 is odd", "({%a+} / .)*"))
+--> the    number    is    odd
+
+-- returns the first numeral in a string
+print(re.match("the number 423 is odd", "s <- {%d+} / . s"))
+--> 423
+
+-- substitutes a dot for each vowel in a string
+print(re.gsub("hello World", "[aeiou]", "."))
+--> h.ll. W.rld
+
+ + +

Balanced parentheses

+

+The following call will produce the same pattern produced by the +Lua expression in the +balanced parentheses example: +

+
+b = re.compile[[  balanced <- "(" ([^()] / balanced)* ")"  ]]
+
+ +

String reversal

+

+The next example reverses a string: +

+
+rev = re.compile[[ R <- (!.) -> '' / ({.} R) -> '%2%1']]
+print(rev:match"0123456789")   --> 9876543210
+
+ +

CSV decoder

+

+The next example replicates the CSV decoder: +

+
+record = re.compile[[
+  record <- {| field (',' field)* |} (%nl / !.)
+  field <- escaped / nonescaped
+  nonescaped <- { [^,"%nl]* }
+  escaped <- '"' {~ ([^"] / '""' -> '"')* ~} '"'
+]]
+
+ +

Lua's long strings

+

+The next example matches Lua long strings: +

+
+c = re.compile([[
+  longstring <- ('[' {:eq: '='* :} '[' close)
+  close <- ']' =eq ']' / . close
+]])
+
+print(c:match'[==[]]===]]]]==]===[]')   --> 17
+
+ +

Abstract Syntax Trees

+

+This example shows a simple way to build an +abstract syntax tree (AST) for a given grammar. +To keep our example simple, +let us consider the following grammar +for lists of names: +

+
+p = re.compile[[
+      listname <- (name s)*
+      name <- [a-z][a-z]*
+      s <- %s*
+]]
+
+

+Now, we will add captures to build a corresponding AST. +As a first step, the pattern will build a table to +represent each non terminal; +terminals will be represented by their corresponding strings: +

+
+c = re.compile[[
+      listname <- {| (name s)* |}
+      name <- {| {[a-z][a-z]*} |}
+      s <- %s*
+]]
+
+

+Now, a match against "hi hello bye" +results in the table +{{"hi"}, {"hello"}, {"bye"}}. +

+

+For such a simple grammar, +this AST is more than enough; +actually, the tables around each single name +are already overkilling. +More complex grammars, +however, may need some more structure. +Specifically, +it would be useful if each table had +a tag field telling what non terminal +that table represents. +We can add such a tag using +named group captures: +

+
+x = re.compile[[
+      listname <- {| {:tag: '' -> 'list':} (name s)* |}
+      name <- {| {:tag: '' -> 'id':} {[a-z][a-z]*} |}
+      s <- ' '*
+]]
+
+

+With these group captures, +a match against "hi hello bye" +results in the following table: +

+
+{tag="list",
+  {tag="id", "hi"},
+  {tag="id", "hello"},
+  {tag="id", "bye"}
+}
+
+ + +

Indented blocks

+

+This example breaks indented blocks into tables, +respecting the indentation: +

+
+p = re.compile[[
+  block <- {| {:ident:' '*:} line
+           ((=ident !' ' line) / &(=ident ' ') block)* |}
+  line <- {[^%nl]*} %nl
+]]
+
+

+As an example, +consider the following text: +

+
+t = p:match[[
+first line
+  subline 1
+  subline 2
+second line
+third line
+  subline 3.1
+    subline 3.1.1
+  subline 3.2
+]]
+
+

+The resulting table t will be like this: +

+
+   {'first line'; {'subline 1'; 'subline 2'; ident = '  '};
+    'second line';
+    'third line'; { 'subline 3.1'; {'subline 3.1.1'; ident = '    '};
+                    'subline 3.2'; ident = '  '};
+    ident = ''}
+
+ +

Macro expander

+

+This example implements a simple macro expander. +Macros must be defined as part of the pattern, +following some simple rules: +

+
+p = re.compile[[
+      text <- {~ item* ~}
+      item <- macro / [^()] / '(' item* ')'
+      arg <- ' '* {~ (!',' item)* ~}
+      args <- '(' arg (',' arg)* ')'
+      -- now we define some macros
+      macro <- ('apply' args) -> '%1(%2)'
+             / ('add' args) -> '%1 + %2'
+             / ('mul' args) -> '%1 * %2'
+]]
+
+print(p:match"add(mul(a,b), apply(f,x))")   --> a * b + f(x)
+
+

+A text is a sequence of items, +wherein we apply a substitution capture to expand any macros. +An item is either a macro, +any character different from parentheses, +or a parenthesized expression. +A macro argument (arg) is a sequence +of items different from a comma. +(Note that a comma may appear inside an item, +e.g., inside a parenthesized expression.) +Again we do a substitution capture to expand any macro +in the argument before expanding the outer macro. +args is a list of arguments separated by commas. +Finally we define the macros. +Each macro is a string substitution; +it replaces the macro name and its arguments by its corresponding string, +with each %n replaced by the n-th argument. +

+ +

Patterns

+

+This example shows the complete syntax +of patterns accepted by re. +

+
+p = [=[
+
+pattern         <- exp !.
+exp             <- S (grammar / alternative)
+
+alternative     <- seq ('/' S seq)*
+seq             <- prefix*
+prefix          <- '&' S prefix / '!' S prefix / suffix
+suffix          <- primary S (([+*?]
+                            / '^' [+-]? num
+                            / '->' S (string / '{}' / name)
+                            / '>>' S name
+                            / '=>' S name) S)*
+
+primary         <- '(' exp ')' / string / class / defined
+                 / '{:' (name ':')? exp ':}'
+                 / '=' name
+                 / '{}'
+                 / '{~' exp '~}'
+                 / '{|' exp '|}'
+                 / '{' exp '}'
+                 / '.'
+                 / name S !arrow
+                 / '<' name '>'          -- old-style non terminals
+
+grammar         <- definition+
+definition      <- name S arrow exp
+
+class           <- '[' '^'? item (!']' item)* ']'
+item            <- defined / range / .
+range           <- . '-' [^]]
+
+S               <- (%s / '--' [^%nl]*)*   -- spaces and comments
+name            <- [A-Za-z_][A-Za-z0-9_]*
+arrow           <- '<-'
+num             <- [0-9]+
+string          <- '"' [^"]* '"' / "'" [^']* "'"
+defined         <- '%' name
+
+]=]
+
+print(re.match(p, p))   -- a self description must match itself
+
+ + + +

License

+ +

+This module is part of the LPeg package and shares +its license. + + +

+ +
+ +
+ + + diff --git a/third_party/lpeg/re.lua b/third_party/lpeg/re.lua new file mode 100644 index 000000000..1fb9fa9ec --- /dev/null +++ b/third_party/lpeg/re.lua @@ -0,0 +1,270 @@ +-- +-- Copyright 2007-2023, Lua.org & PUC-Rio (see 'lpeg.html' for license) +-- written by Roberto Ierusalimschy +-- + +-- imported functions and modules +local tonumber, type, print, error = tonumber, type, print, error +local setmetatable = setmetatable +local m = require"lpeg" + +-- 'm' will be used to parse expressions, and 'mm' will be used to +-- create expressions; that is, 're' runs on 'm', creating patterns +-- on 'mm' +local mm = m + +-- patterns' metatable +local mt = getmetatable(mm.P(0)) + + +local version = _VERSION + +-- No more global accesses after this point +_ENV = nil -- does no harm in Lua 5.1 + + +local any = m.P(1) + + +-- Pre-defined names +local Predef = { nl = m.P"\n" } + + +local mem +local fmem +local gmem + + +local function updatelocale () + mm.locale(Predef) + Predef.a = Predef.alpha + Predef.c = Predef.cntrl + Predef.d = Predef.digit + Predef.g = Predef.graph + Predef.l = Predef.lower + Predef.p = Predef.punct + Predef.s = Predef.space + Predef.u = Predef.upper + Predef.w = Predef.alnum + Predef.x = Predef.xdigit + Predef.A = any - Predef.a + Predef.C = any - Predef.c + Predef.D = any - Predef.d + Predef.G = any - Predef.g + Predef.L = any - Predef.l + Predef.P = any - Predef.p + Predef.S = any - Predef.s + Predef.U = any - Predef.u + Predef.W = any - Predef.w + Predef.X = any - Predef.x + mem = {} -- restart memoization + fmem = {} + gmem = {} + local mt = {__mode = "v"} + setmetatable(mem, mt) + setmetatable(fmem, mt) + setmetatable(gmem, mt) +end + + +updatelocale() + + + +local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end) + + +local function patt_error (s, i) + local msg = (#s < i + 20) and s:sub(i) + or s:sub(i,i+20) .. "..." + msg = ("pattern error near '%s'"):format(msg) + error(msg, 2) +end + +local function mult (p, n) + local np = mm.P(true) + while n >= 1 do + if n%2 >= 1 then np = np * p end + p = p * p + n = n/2 + end + return np +end + +local function equalcap (s, i, c) + if type(c) ~= "string" then return nil end + local e = #c + i + if s:sub(i, e - 1) == c then return e else return nil end +end + + +local S = (Predef.space + "--" * (any - Predef.nl)^0)^0 + +local name = m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0 + +local arrow = S * "<-" + +local seq_follow = m.P"/" + ")" + "}" + ":}" + "~}" + "|}" + (name * arrow) + -1 + +name = m.C(name) + + +-- a defined name only have meaning in a given environment +local Def = name * m.Carg(1) + + +local function getdef (id, defs) + local c = defs and defs[id] + if not c then error("undefined name: " .. id) end + return c +end + +-- match a name and return a group of its corresponding definition +-- and 'f' (to be folded in 'Suffix') +local function defwithfunc (f) + return m.Cg(Def / getdef * m.Cc(f)) +end + + +local num = m.C(m.R"09"^1) * S / tonumber + +local String = "'" * m.C((any - "'")^0) * "'" + + '"' * m.C((any - '"')^0) * '"' + + +local defined = "%" * Def / function (c,Defs) + local cat = Defs and Defs[c] or Predef[c] + if not cat then error ("name '" .. c .. "' undefined") end + return cat +end + +local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R + +local item = (defined + Range + m.C(any)) / m.P + +local Class = + "[" + * (m.C(m.P"^"^-1)) -- optional complement symbol + * (item * ((item % mt.__add) - "]")^0) / + function (c, p) return c == "^" and any - p or p end + * "]" + +local function adddef (t, k, exp) + if t[k] then + error("'"..k.."' already defined as a rule") + else + t[k] = exp + end + return t +end + +local function firstdef (n, r) return adddef({n}, n, r) end + + +local function NT (n, b) + if not b then + error("rule '"..n.."' used outside a grammar") + else return mm.V(n) + end +end + + +local exp = m.P{ "Exp", + Exp = S * ( m.V"Grammar" + + m.V"Seq" * ("/" * S * m.V"Seq" % mt.__add)^0 ); + Seq = (m.Cc(m.P"") * (m.V"Prefix" % mt.__mul)^0) + * (#seq_follow + patt_error); + Prefix = "&" * S * m.V"Prefix" / mt.__len + + "!" * S * m.V"Prefix" / mt.__unm + + m.V"Suffix"; + Suffix = m.V"Primary" * S * + ( ( m.P"+" * m.Cc(1, mt.__pow) + + m.P"*" * m.Cc(0, mt.__pow) + + m.P"?" * m.Cc(-1, mt.__pow) + + "^" * ( m.Cg(num * m.Cc(mult)) + + m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow)) + ) + + "->" * S * ( m.Cg((String + num) * m.Cc(mt.__div)) + + m.P"{}" * m.Cc(nil, m.Ct) + + defwithfunc(mt.__div) + ) + + "=>" * S * defwithfunc(mm.Cmt) + + ">>" * S * defwithfunc(mt.__mod) + + "~>" * S * defwithfunc(mm.Cf) + ) % function (a,b,f) return f(a,b) end * S + )^0; + Primary = "(" * m.V"Exp" * ")" + + String / mm.P + + Class + + defined + + "{:" * (name * ":" + m.Cc(nil)) * m.V"Exp" * ":}" / + function (n, p) return mm.Cg(p, n) end + + "=" * name / function (n) return mm.Cmt(mm.Cb(n), equalcap) end + + m.P"{}" / mm.Cp + + "{~" * m.V"Exp" * "~}" / mm.Cs + + "{|" * m.V"Exp" * "|}" / mm.Ct + + "{" * m.V"Exp" * "}" / mm.C + + m.P"." * m.Cc(any) + + (name * -arrow + "<" * name * ">") * m.Cb("G") / NT; + Definition = name * arrow * m.V"Exp"; + Grammar = m.Cg(m.Cc(true), "G") * + ((m.V"Definition" / firstdef) * (m.V"Definition" % adddef)^0) / mm.P +} + +local pattern = S * m.Cg(m.Cc(false), "G") * exp / mm.P * (-any + patt_error) + + +local function compile (p, defs) + if mm.type(p) == "pattern" then return p end -- already compiled + local cp = pattern:match(p, 1, defs) + if not cp then error("incorrect pattern", 3) end + return cp +end + +local function match (s, p, i) + local cp = mem[p] + if not cp then + cp = compile(p) + mem[p] = cp + end + return cp:match(s, i or 1) +end + +local function find (s, p, i) + local cp = fmem[p] + if not cp then + cp = compile(p) / 0 + cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) } + fmem[p] = cp + end + local i, e = cp:match(s, i or 1) + if i then return i, e - 1 + else return i + end +end + +local function gsub (s, p, rep) + local g = gmem[p] or {} -- ensure gmem[p] is not collected while here + gmem[p] = g + local cp = g[rep] + if not cp then + cp = compile(p) + cp = mm.Cs((cp / rep + 1)^0) + g[rep] = cp + end + return cp:match(s) +end + + +-- exported names +local re = { + compile = compile, + match = match, + find = find, + gsub = gsub, + updatelocale = updatelocale, +} + +if version == "Lua 5.1" then _G.re = re end + +return re diff --git a/third_party/lpeg/test.lua b/third_party/lpeg/test.lua new file mode 100644 index 000000000..d0b82da48 --- /dev/null +++ b/third_party/lpeg/test.lua @@ -0,0 +1,1667 @@ +#!/usr/bin/env lua + +-- require"strict" -- just to be pedantic + +local m = require"lpeg" + + +-- for general use +local a, b, c, d, e, f, g, p, t + + +-- compatibility with Lua 5.2 +local unpack = rawget(table, "unpack") or unpack +local loadstring = rawget(_G, "loadstring") or load + + +local any = m.P(1) +local space = m.S" \t\n"^0 + +local function checkeq (x, y, p) +if p then print(x,y) end + if type(x) ~= "table" then assert(x == y) + else + for k,v in pairs(x) do checkeq(v, y[k], p) end + for k,v in pairs(y) do checkeq(v, x[k], p) end + end +end + + +local mt = getmetatable(m.P(1)) + + +local allchar = {} +for i=0,255 do allchar[i + 1] = i end +allchar = string.char(unpack(allchar)) +assert(#allchar == 256) + +local function cs2str (c) + return m.match(m.Cs((c + m.P(1)/"")^0), allchar) +end + +local function eqcharset (c1, c2) + assert(cs2str(c1) == cs2str(c2)) +end + + +print"General tests for LPeg library" + +assert(type(m.version) == "string") +print(m.version) +assert(m.type("alo") ~= "pattern") +assert(m.type(io.input) ~= "pattern") +assert(m.type(m.P"alo") == "pattern") + +-- tests for some basic optimizations +assert(m.match(m.P(false) + "a", "a") == 2) +assert(m.match(m.P(true) + "a", "a") == 1) +assert(m.match("a" + m.P(false), "b") == nil) +assert(m.match("a" + m.P(true), "b") == 1) + +assert(m.match(m.P(false) * "a", "a") == nil) +assert(m.match(m.P(true) * "a", "a") == 2) +assert(m.match("a" * m.P(false), "a") == nil) +assert(m.match("a" * m.P(true), "a") == 2) + +assert(m.match(#m.P(false) * "a", "a") == nil) +assert(m.match(#m.P(true) * "a", "a") == 2) +assert(m.match("a" * #m.P(false), "a") == nil) +assert(m.match("a" * #m.P(true), "a") == 2) + +assert(m.match(m.P(1)^0, "abcd") == 5) +assert(m.match(m.S("")^0, "abcd") == 1) + +-- tests for locale +do + assert(m.locale(m) == m) + local t = {} + assert(m.locale(t, m) == t) + local x = m.locale() + for n,v in pairs(x) do + assert(type(n) == "string") + eqcharset(v, m[n]) + end +end + + +assert(m.match(3, "aaaa")) +assert(m.match(4, "aaaa")) +assert(not m.match(5, "aaaa")) +assert(m.match(-3, "aa")) +assert(not m.match(-3, "aaa")) +assert(not m.match(-3, "aaaa")) +assert(not m.match(-4, "aaaa")) +assert(m.P(-5):match"aaaa") + +assert(m.match("a", "alo") == 2) +assert(m.match("al", "alo") == 3) +assert(not m.match("alu", "alo")) +assert(m.match(true, "") == 1) + +local digit = m.S"0123456789" +local upper = m.S"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +local lower = m.S"abcdefghijklmnopqrstuvwxyz" +local letter = m.S"" + upper + lower +local alpha = letter + digit + m.R() + +eqcharset(m.S"", m.P(false)) +eqcharset(upper, m.R("AZ")) +eqcharset(lower, m.R("az")) +eqcharset(upper + lower, m.R("AZ", "az")) +eqcharset(upper + lower, m.R("AZ", "cz", "aa", "bb", "90")) +eqcharset(digit, m.S"01234567" + "8" + "9") +eqcharset(upper, letter - lower) +eqcharset(m.S(""), m.R()) +assert(cs2str(m.S("")) == "") + +eqcharset(m.S"\0", "\0") +eqcharset(m.S"\1\0\2", m.R"\0\2") +eqcharset(m.S"\1\0\2", m.R"\1\2" + "\0") +eqcharset(m.S"\1\0\2" - "\0", m.R"\1\2") + +eqcharset(m.S("\0\255"), m.P"\0" + "\255") -- charset extremes + +local word = alpha^1 * (1 - alpha)^0 + +assert((word^0 * -1):match"alo alo") +assert(m.match(word^1 * -1, "alo alo")) +assert(m.match(word^2 * -1, "alo alo")) +assert(not m.match(word^3 * -1, "alo alo")) + +assert(not m.match(word^-1 * -1, "alo alo")) +assert(m.match(word^-2 * -1, "alo alo")) +assert(m.match(word^-3 * -1, "alo alo")) + +local eos = m.P(-1) + +assert(m.match(digit^0 * letter * digit * eos, "1298a1")) +assert(not m.match(digit^0 * letter * eos, "1257a1")) + +b = { + [1] = "(" * (((1 - m.S"()") + #m.P"(" * m.V(1))^0) * ")" +} + +assert(m.match(b, "(al())()")) +assert(not m.match(b * eos, "(al())()")) +assert(m.match(b * eos, "((al())()(é))")) +assert(not m.match(b, "(al()()")) + +assert(not m.match(letter^1 - "for", "foreach")) +assert(m.match(letter^1 - ("for" * eos), "foreach")) +assert(not m.match(letter^1 - ("for" * eos), "for")) + +function basiclookfor (p) + return m.P { + [1] = p + (1 * m.V(1)) + } +end + +function caplookfor (p) + return basiclookfor(p:C()) +end + +assert(m.match(caplookfor(letter^1), " 4achou123...") == "achou") +a = {m.match(caplookfor(letter^1)^0, " two words, one more ")} +checkeq(a, {"two", "words", "one", "more"}) + +assert(m.match( basiclookfor((#m.P(b) * 1) * m.Cp()), " ( (a)") == 7) + +a = {m.match(m.C(digit^1 * m.Cc"d") + m.C(letter^1 * m.Cc"l"), "123")} +checkeq(a, {"123", "d"}) + +-- bug in LPeg 0.12 (nil value does not create a 'ktable') +assert(m.match(m.Cc(nil), "") == nil) + +a = {m.match(m.C(digit^1 * m.Cc"d") + m.C(letter^1 * m.Cc"l"), "abcd")} +checkeq(a, {"abcd", "l"}) + +a = {m.match(m.Cc(10,20,30) * 'a' * m.Cp(), 'aaa')} +checkeq(a, {10,20,30,2}) +a = {m.match(m.Cp() * m.Cc(10,20,30) * 'a' * m.Cp(), 'aaa')} +checkeq(a, {1,10,20,30,2}) +a = m.match(m.Ct(m.Cp() * m.Cc(10,20,30) * 'a' * m.Cp()), 'aaa') +checkeq(a, {1,10,20,30,2}) +a = m.match(m.Ct(m.Cp() * m.Cc(7,8) * m.Cc(10,20,30) * 'a' * m.Cp()), 'aaa') +checkeq(a, {1,7,8,10,20,30,2}) +a = {m.match(m.Cc() * m.Cc() * m.Cc(1) * m.Cc(2,3,4) * m.Cc() * 'a', 'aaa')} +checkeq(a, {1,2,3,4}) + +a = {m.match(m.Cp() * letter^1 * m.Cp(), "abcd")} +checkeq(a, {1, 5}) + + +t = {m.match({[1] = m.C(m.C(1) * m.V(1) + -1)}, "abc")} +checkeq(t, {"abc", "a", "bc", "b", "c", "c", ""}) + +-- bug in 0.12 ('hascapture' did not check for captures inside a rule) +do + local pat = m.P{ + 'S'; + S1 = m.C('abc') + 3, + S = #m.V('S1') -- rule has capture, but '#' must ignore it + } + assert(pat:match'abc' == 1) +end + + +-- bug: loop in 'hascaptures' +do + local p = m.C(-m.P{m.P'x' * m.V(1) + m.P'y'}) + assert(p:match("xxx") == "") +end + + + +-- test for small capture boundary +for i = 250,260 do + assert(#m.match(m.C(i), string.rep('a', i)) == i) + assert(#m.match(m.C(m.C(i)), string.rep('a', i)) == i) +end + +-- tests for any*n and any*-n +for n = 1, 550, 13 do + local x_1 = string.rep('x', n - 1) + local x = x_1 .. 'a' + assert(not m.P(n):match(x_1)) + assert(m.P(n):match(x) == n + 1) + assert(n < 4 or m.match(m.P(n) + "xxx", x_1) == 4) + assert(m.C(n):match(x) == x) + assert(m.C(m.C(n)):match(x) == x) + assert(m.P(-n):match(x_1) == 1) + assert(not m.P(-n):match(x)) + assert(n < 13 or m.match(m.Cc(20) * ((n - 13) * m.P(10)) * 3, x) == 20) + local n3 = math.floor(n/3) + assert(m.match(n3 * m.Cp() * n3 * n3, x) == n3 + 1) +end + +-- true values +assert(m.P(0):match("x") == 1) +assert(m.P(0):match("") == 1) +assert(m.C(0):match("x") == "") + +assert(m.match(m.Cc(0) * m.P(10) + m.Cc(1) * "xuxu", "xuxu") == 1) +assert(m.match(m.Cc(0) * m.P(10) + m.Cc(1) * "xuxu", "xuxuxuxuxu") == 0) +assert(m.match(m.C(m.P(2)^1), "abcde") == "abcd") +p = m.Cc(0) * 1 + m.Cc(1) * 2 + m.Cc(2) * 3 + m.Cc(3) * 4 + + +-- test for alternation optimization +assert(m.match(m.P"a"^1 + "ab" + m.P"x"^0, "ab") == 2) +assert(m.match((m.P"a"^1 + "ab" + m.P"x"^0 * 1)^0, "ab") == 3) +assert(m.match(m.P"ab" + "cd" + "" + "cy" + "ak", "98") == 1) +assert(m.match(m.P"ab" + "cd" + "ax" + "cy", "ax") == 3) +assert(m.match("a" * m.P"b"^0 * "c" + "cd" + "ax" + "cy", "ax") == 3) +assert(m.match((m.P"ab" + "cd" + "ax" + "cy")^0, "ax") == 3) +assert(m.match(m.P(1) * "x" + m.S"" * "xu" + "ay", "ay") == 3) +assert(m.match(m.P"abc" + "cde" + "aka", "aka") == 4) +assert(m.match(m.S"abc" * "x" + "cde" + "aka", "ax") == 3) +assert(m.match(m.S"abc" * "x" + "cde" + "aka", "aka") == 4) +assert(m.match(m.S"abc" * "x" + "cde" + "aka", "cde") == 4) +assert(m.match(m.S"abc" * "x" + "ide" + m.S"ab" * "ka", "aka") == 4) +assert(m.match("ab" + m.S"abc" * m.P"y"^0 * "x" + "cde" + "aka", "ax") == 3) +assert(m.match("ab" + m.S"abc" * m.P"y"^0 * "x" + "cde" + "aka", "aka") == 4) +assert(m.match("ab" + m.S"abc" * m.P"y"^0 * "x" + "cde" + "aka", "cde") == 4) +assert(m.match("ab" + m.S"abc" * m.P"y"^0 * "x" + "ide" + m.S"ab" * "ka", "aka") == 4) +assert(m.match("ab" + m.S"abc" * m.P"y"^0 * "x" + "ide" + m.S"ab" * "ka", "ax") == 3) +assert(m.match(m.P(1) * "x" + "cde" + m.S"ab" * "ka", "aka") == 4) +assert(m.match(m.P(1) * "x" + "cde" + m.P(1) * "ka", "aka") == 4) +assert(m.match(m.P(1) * "x" + "cde" + m.P(1) * "ka", "cde") == 4) +assert(m.match(m.P"eb" + "cd" + m.P"e"^0 + "x", "ee") == 3) +assert(m.match(m.P"ab" + "cd" + m.P"e"^0 + "x", "abcd") == 3) +assert(m.match(m.P"ab" + "cd" + m.P"e"^0 + "x", "eeex") == 4) +assert(m.match(m.P"ab" + "cd" + m.P"e"^0 + "x", "cd") == 3) +assert(m.match(m.P"ab" + "cd" + m.P"e"^0 + "x", "x") == 1) +assert(m.match(m.P"ab" + "cd" + m.P"e"^0 + "x" + "", "zee") == 1) +assert(m.match(m.P"ab" + "cd" + m.P"e"^1 + "x", "abcd") == 3) +assert(m.match(m.P"ab" + "cd" + m.P"e"^1 + "x", "eeex") == 4) +assert(m.match(m.P"ab" + "cd" + m.P"e"^1 + "x", "cd") == 3) +assert(m.match(m.P"ab" + "cd" + m.P"e"^1 + "x", "x") == 2) +assert(m.match(m.P"ab" + "cd" + m.P"e"^1 + "x" + "", "zee") == 1) +assert(not m.match(("aa" * m.P"bc"^-1 + "aab") * "e", "aabe")) + +assert(m.match("alo" * (m.P"\n" + -1), "alo") == 4) + + +-- bug in 0.12 (rc1) +assert(m.match((m.P"\128\187\191" + m.S"abc")^0, "\128\187\191") == 4) + +assert(m.match(m.S"\0\128\255\127"^0, string.rep("\0\128\255\127", 10)) == + 4*10 + 1) + +-- optimizations with optional parts +assert(m.match(("ab" * -m.P"c")^-1, "abc") == 1) +assert(m.match(("ab" * #m.P"c")^-1, "abd") == 1) +assert(m.match(("ab" * m.B"c")^-1, "ab") == 1) +assert(m.match(("ab" * m.P"cd"^0)^-1, "abcdcdc") == 7) + +assert(m.match(m.P"ab"^-1 - "c", "abcd") == 3) + +p = ('Aa' * ('Bb' * ('Cc' * m.P'Dd'^0)^0)^0)^-1 +assert(p:match("AaBbCcDdBbCcDdDdDdBb") == 21) + + +-- bug in 0.12.2 +-- p = { ('ab' ('c' 'ef'?)*)? } +p = m.C(('ab' * ('c' * m.P'ef'^-1)^0)^-1) +s = "abcefccefc" +assert(s == p:match(s)) + + +pi = "3.14159 26535 89793 23846 26433 83279 50288 41971 69399 37510" +assert(m.match(m.Cs((m.P"1" / "a" + m.P"5" / "b" + m.P"9" / "c" + 1)^0), pi) == + m.match(m.Cs((m.P(1) / {["1"] = "a", ["5"] = "b", ["9"] = "c"})^0), pi)) +print"+" + + +-- tests for capture optimizations +assert(m.match((m.P(3) + 4 * m.Cp()) * "a", "abca") == 5) +t = {m.match(((m.P"a" + m.Cp()) * m.P"x")^0, "axxaxx")} +checkeq(t, {3, 6}) + + +-- tests for numbered captures +p = m.C(1) +assert(m.match(m.C(m.C(p * m.C(2)) * m.C(3)) / 3, "abcdefgh") == "a") +assert(m.match(m.C(m.C(p * m.C(2)) * m.C(3)) / 1, "abcdefgh") == "abcdef") +assert(m.match(m.C(m.C(p * m.C(2)) * m.C(3)) / 4, "abcdefgh") == "bc") +assert(m.match(m.C(m.C(p * m.C(2)) * m.C(3)) / 0, "abcdefgh") == 7) + +a, b, c = m.match(p * (m.C(p * m.C(2)) * m.C(3) / 4) * p, "abcdefgh") +assert(a == "a" and b == "efg" and c == "h") + +-- test for table captures +t = m.match(m.Ct(letter^1), "alo") +checkeq(t, {}) + +t, n = m.match(m.Ct(m.C(letter)^1) * m.Cc"t", "alo") +assert(n == "t" and table.concat(t) == "alo") + +t = m.match(m.Ct(m.C(m.C(letter)^1)), "alo") +assert(table.concat(t, ";") == "alo;a;l;o") + +t = m.match(m.Ct(m.C(m.C(letter)^1)), "alo") +assert(table.concat(t, ";") == "alo;a;l;o") + +t = m.match(m.Ct(m.Ct((m.Cp() * letter * m.Cp())^1)), "alo") +assert(table.concat(t[1], ";") == "1;2;2;3;3;4") + +t = m.match(m.Ct(m.C(m.C(1) * 1 * m.C(1))), "alo") +checkeq(t, {"alo", "a", "o"}) + + +-- tests for groups +p = m.Cg(1) -- no capture +assert(p:match('x') == 'x') +p = m.Cg(m.P(true)/function () end * 1) -- no value +assert(p:match('x') == 'x') +p = m.Cg(m.Cg(m.Cg(m.C(1)))) +assert(p:match('x') == 'x') +p = m.Cg(m.Cg(m.Cg(m.C(1))^0) * m.Cg(m.Cc(1) * m.Cc(2))) +t = {p:match'abc'} +checkeq(t, {'a', 'b', 'c', 1, 2}) + +p = m.Ct(m.Cg(m.Cc(10), "hi") * m.C(1)^0 * m.Cg(m.Cc(20), "ho")) +t = p:match'' +checkeq(t, {hi = 10, ho = 20}) +t = p:match'abc' +checkeq(t, {hi = 10, ho = 20, 'a', 'b', 'c'}) + +-- non-string group names +p = m.Ct(m.Cg(1, print) * m.Cg(1, 23.5) * m.Cg(1, io)) +t = p:match('abcdefghij') +assert(t[print] == 'a' and t[23.5] == 'b' and t[io] == 'c') + + +-- test for error messages +local function checkerr (msg, f, ...) + local st, err = pcall(f, ...) + assert(not st and m.match({ m.P(msg) + 1 * m.V(1) }, err)) +end + +checkerr("rule '1' may be left recursive", m.match, { m.V(1) * 'a' }, "a") +checkerr("rule '1' used outside a grammar", m.match, m.V(1), "") +checkerr("rule 'hiii' used outside a grammar", m.match, m.V('hiii'), "") +checkerr("rule 'hiii' undefined in given grammar", m.match, { m.V('hiii') }, "") +checkerr("undefined in given grammar", m.match, { m.V{} }, "") + +checkerr("rule 'A' is not a pattern", m.P, { m.P(1), A = {} }) +checkerr("grammar has no initial rule", m.P, { [print] = {} }) + +-- grammar with a long call chain before left recursion +p = {'a', + a = m.V'b' * m.V'c' * m.V'd' * m.V'a', + b = m.V'c', + c = m.V'd', + d = m.V'e', + e = m.V'f', + f = m.V'g', + g = m.P'' +} +checkerr("rule 'a' may be left recursive", m.match, p, "a") + +-- Bug in peephole optimization of LPeg 0.12 (IJmp -> ICommit) +-- the next grammar has an original sequence IJmp -> ICommit -> IJmp L1 +-- that is optimized to ICommit L1 + +p = m.P { (m.P {m.P'abc'} + 'ayz') * m.V'y'; y = m.P'x' } +assert(p:match('abcx') == 5 and p:match('ayzx') == 5 and not p:match'abc') + + +do + print "testing large dynamic Cc" + local lim = 2^16 - 1 + local c = 0 + local function seq (n) + if n == 1 then c = c + 1; return m.Cc(c) + else + local m = math.floor(n / 2) + return seq(m) * seq(n - m) + end + end + p = m.Ct(seq(lim)) + t = p:match('') + assert(t[lim] == lim) + checkerr("too many", function () p = p / print end) + checkerr("too many", seq, lim + 1) +end + + +do + -- nesting of captures too deep + local p = m.C(1) + for i = 1, 300 do + p = m.Ct(p) + end + checkerr("too deep", p.match, p, "x") +end + + +-- tests for non-pattern as arguments to pattern functions + +p = { ('a' * m.V(1))^-1 } * m.P'b' * { 'a' * m.V(2); m.V(1)^-1 } +assert(m.match(p, "aaabaac") == 7) + +p = m.P'abc' * 2 * -5 * true * 'de' -- mix of numbers and strings and booleans + +assert(p:match("abc01de") == 8) +assert(p:match("abc01de3456") == nil) + +p = 'abc' * (2 * (-5 * (true * m.P'de'))) + +assert(p:match("abc01de") == 8) +assert(p:match("abc01de3456") == nil) + +p = { m.V(2), m.P"abc" } * + (m.P{ "xx", xx = m.P"xx" } + { "x", x = m.P"a" * m.V"x" + "" }) +assert(p:match("abcaaaxx") == 7) +assert(p:match("abcxx") == 6) + + +-- a large table capture +t = m.match(m.Ct(m.C('a')^0), string.rep("a", 10000)) +assert(#t == 10000 and t[1] == 'a' and t[#t] == 'a') + +print('+') + + +-- bug in 0.10 (rechecking a grammar, after tail-call optimization) +m.P{ m.P { (m.P(3) + "xuxu")^0 * m.V"xuxu", xuxu = m.P(1) } } + +local V = m.V + +local Space = m.S(" \n\t")^0 +local Number = m.C(m.R("09")^1) * Space +local FactorOp = m.C(m.S("+-")) * Space +local TermOp = m.C(m.S("*/")) * Space +local Open = "(" * Space +local Close = ")" * Space + + +local function f_factor (v1, op, v2, d) + assert(d == nil) + if op == "+" then return v1 + v2 + else return v1 - v2 + end +end + + +local function f_term (v1, op, v2, d) + assert(d == nil) + if op == "*" then return v1 * v2 + else return v1 / v2 + end +end + +G = m.P{ "Exp", + Exp = V"Factor" * (FactorOp * V"Factor" % f_factor)^0; + Factor = V"Term" * (TermOp * V"Term" % f_term)^0; + Term = Number / tonumber + Open * V"Exp" * Close; +} + +G = Space * G * -1 + +for _, s in ipairs{" 3 + 5*9 / (1+1) ", "3+4/2", "3+3-3- 9*2+3*9/1- 8"} do + assert(m.match(G, s) == loadstring("return "..s)()) +end + + +-- test for grammars (errors deep in calling non-terminals) +g = m.P{ + [1] = m.V(2) + "a", + [2] = "a" * m.V(3) * "x", + [3] = "b" * m.V(3) + "c" +} + +assert(m.match(g, "abbbcx") == 7) +assert(m.match(g, "abbbbx") == 2) + + +-- tests for \0 +assert(m.match(m.R("\0\1")^1, "\0\1\0") == 4) +assert(m.match(m.S("\0\1ab")^1, "\0\1\0a") == 5) +assert(m.match(m.P(1)^3, "\0\1\0a") == 5) +assert(not m.match(-4, "\0\1\0a")) +assert(m.match("\0\1\0a", "\0\1\0a") == 5) +assert(m.match("\0\0\0", "\0\0\0") == 4) +assert(not m.match("\0\0\0", "\0\0")) + + +-- tests for predicates +assert(not m.match(-m.P("a") * 2, "alo")) +assert(m.match(- -m.P("a") * 2, "alo") == 3) +assert(m.match(#m.P("a") * 2, "alo") == 3) +assert(m.match(##m.P("a") * 2, "alo") == 3) +assert(not m.match(##m.P("c") * 2, "alo")) +assert(m.match(m.Cs((##m.P("a") * 1 + m.P(1)/".")^0), "aloal") == "a..a.") +assert(m.match(m.Cs((#((#m.P"a")/"") * 1 + m.P(1)/".")^0), "aloal") == "a..a.") +assert(m.match(m.Cs((- -m.P("a") * 1 + m.P(1)/".")^0), "aloal") == "a..a.") +assert(m.match(m.Cs((-((-m.P"a")/"") * 1 + m.P(1)/".")^0), "aloal") == "a..a.") + + +-- fixed length +do + -- 'and' predicate using fixed length + local p = m.C(#("a" * (m.P("bd") + "cd")) * 2) + assert(p:match("acd") == "ac") + + p = #m.P{ "a" * m.V(2), m.P"b" } * 2 + assert(p:match("abc") == 3) + + p = #(m.P"abc" * m.B"c") + assert(p:match("abc") == 1 and not p:match("ab")) + + p = m.P{ "a" * m.V(2), m.P"b"^1 } + checkerr("pattern may not have fixed length", m.B, p) + + p = "abc" * (m.P"b"^1 + m.P"a"^0) + checkerr("pattern may not have fixed length", m.B, p) +end + + +p = -m.P'a' * m.Cc(1) + -m.P'b' * m.Cc(2) + -m.P'c' * m.Cc(3) +assert(p:match('a') == 2 and p:match('') == 1 and p:match('b') == 1) + +p = -m.P'a' * m.Cc(10) + #m.P'a' * m.Cc(20) +assert(p:match('a') == 20 and p:match('') == 10 and p:match('b') == 10) + + + +-- look-behind predicate +assert(not m.match(m.B'a', 'a')) +assert(m.match(1 * m.B'a', 'a') == 2) +assert(not m.match(m.B(1), 'a')) +assert(m.match(1 * m.B(1), 'a') == 2) +assert(m.match(-m.B(1), 'a') == 1) +assert(m.match(m.B(250), string.rep('a', 250)) == nil) +assert(m.match(250 * m.B(250), string.rep('a', 250)) == 251) + +-- look-behind with an open call +checkerr("pattern may not have fixed length", m.B, m.V'S1') +checkerr("too long to look behind", m.B, 260) + +B = #letter * -m.B(letter) + -letter * m.B(letter) +x = m.Ct({ (B * m.Cp())^-1 * (1 * m.V(1) + m.P(true)) }) +checkeq(m.match(x, 'ar cal c'), {1,3,4,7,9,10}) +checkeq(m.match(x, ' ar cal '), {2,4,5,8}) +checkeq(m.match(x, ' '), {}) +checkeq(m.match(x, 'aloalo'), {1,7}) + +assert(m.match(B, "a") == 1) +assert(m.match(1 * B, "a") == 2) +assert(not m.B(1 - letter):match("")) +assert((-m.B(letter)):match("") == 1) + +assert((4 * m.B(letter, 4)):match("aaaaaaaa") == 5) +assert(not (4 * m.B(#letter * 5)):match("aaaaaaaa")) +assert((4 * -m.B(#letter * 5)):match("aaaaaaaa") == 5) + +-- look-behind with grammars +assert(m.match('a' * m.B{'x', x = m.P(3)}, 'aaa') == nil) +assert(m.match('aa' * m.B{'x', x = m.P('aaa')}, 'aaaa') == nil) +assert(m.match('aaa' * m.B{'x', x = m.P('aaa')}, 'aaaaa') == 4) + + + +-- bug in 0.9 +assert(m.match(('a' * #m.P'b'), "ab") == 2) +assert(not m.match(('a' * #m.P'b'), "a")) + +assert(not m.match(#m.S'567', "")) +assert(m.match(#m.S'567' * 1, "6") == 2) + + +-- tests for Tail Calls + +p = m.P{ 'a' * m.V(1) + '' } +assert(p:match(string.rep('a', 1000)) == 1001) + +-- create a grammar for a simple DFA for even number of 0s and 1s +-- +-- ->1 <---0---> 2 +-- ^ ^ +-- | | +-- 1 1 +-- | | +-- V V +-- 3 <---0---> 4 +-- +-- this grammar should keep no backtracking information + +p = m.P{ + [1] = '0' * m.V(2) + '1' * m.V(3) + -1, + [2] = '0' * m.V(1) + '1' * m.V(4), + [3] = '0' * m.V(4) + '1' * m.V(1), + [4] = '0' * m.V(3) + '1' * m.V(2), +} + +assert(p:match(string.rep("00", 10000))) +assert(p:match(string.rep("01", 10000))) +assert(p:match(string.rep("011", 10000))) +assert(not p:match(string.rep("011", 10000) .. "1")) +assert(not p:match(string.rep("011", 10001))) + + +-- this grammar does need backtracking info. +local lim = 10000 +p = m.P{ '0' * m.V(1) + '0' } +checkerr("stack overflow", m.match, p, string.rep("0", lim)) +m.setmaxstack(2*lim) +checkerr("stack overflow", m.match, p, string.rep("0", lim)) +m.setmaxstack(2*lim + 4) +assert(m.match(p, string.rep("0", lim)) == lim + 1) + +-- this repetition should not need stack space (only the call does) +p = m.P{ ('a' * m.V(1))^0 * 'b' + 'c' } +m.setmaxstack(200) +assert(p:match(string.rep('a', 180) .. 'c' .. string.rep('b', 180)) == 362) + +m.setmaxstack(100) -- restore low limit + +-- tests for optional start position +assert(m.match("a", "abc", 1)) +assert(m.match("b", "abc", 2)) +assert(m.match("c", "abc", 3)) +assert(not m.match(1, "abc", 4)) +assert(m.match("a", "abc", -3)) +assert(m.match("b", "abc", -2)) +assert(m.match("c", "abc", -1)) +assert(m.match("abc", "abc", -4)) -- truncate to position 1 + +assert(m.match("", "abc", 10)) -- empty string is everywhere! +assert(m.match("", "", 10)) +assert(not m.match(1, "", 1)) +assert(not m.match(1, "", -1)) +assert(not m.match(1, "", 0)) + +print("+") + + +-- tests for argument captures +checkerr("invalid argument", m.Carg, 0) +checkerr("invalid argument", m.Carg, -1) +checkerr("invalid argument", m.Carg, 2^18) +checkerr("absent extra argument #1", m.match, m.Carg(1), 'a', 1) +assert(m.match(m.Carg(1), 'a', 1, print) == print) +x = {m.match(m.Carg(1) * m.Carg(2), '', 1, 10, 20)} +checkeq(x, {10, 20}) + +assert(m.match(m.Cmt(m.Cg(m.Carg(3), "a") * + m.Cmt(m.Cb("a"), function (s,i,x) + assert(s == "a" and i == 1); + return i, x+1 + end) * + m.Carg(2), function (s,i,a,b,c) + assert(s == "a" and i == 1 and c == nil); + return i, 2*a + 3*b + end) * "a", + "a", 1, false, 100, 1000) == 2*1001 + 3*100) + + +-- tests for Lua functions + +t = {} +s = "" +p = m.P(function (s1, i) assert(s == s1); t[#t + 1] = i; return nil end) * false +s = "hi, this is a test" +assert(m.match(((p - m.P(-1)) + 2)^0, s) == string.len(s) + 1) +assert(#t == string.len(s)/2 and t[1] == 1 and t[2] == 3) + +assert(not m.match(p, s)) + +p = mt.__add(function (s, i) return i end, function (s, i) return nil end) +assert(m.match(p, "alo")) + +p = mt.__mul(function (s, i) return i end, function (s, i) return nil end) +assert(not m.match(p, "alo")) + + +t = {} +p = function (s1, i) assert(s == s1); t[#t + 1] = i; return i end +s = "hi, this is a test" +assert(m.match((m.P(1) * p)^0, s) == string.len(s) + 1) +assert(#t == string.len(s) and t[1] == 2 and t[2] == 3) + +t = {} +p = m.P(function (s1, i) assert(s == s1); t[#t + 1] = i; + return i <= s1:len() and i end) * 1 +s = "hi, this is a test" +assert(m.match(p^0, s) == string.len(s) + 1) +assert(#t == string.len(s) + 1 and t[1] == 1 and t[2] == 2) + +p = function (s1, i) return m.match(m.P"a"^1, s1, i) end +assert(m.match(p, "aaaa") == 5) +assert(m.match(p, "abaa") == 2) +assert(not m.match(p, "baaa")) + +checkerr("invalid position", m.match, function () return 2^20 end, s) +checkerr("invalid position", m.match, function () return 0 end, s) +checkerr("invalid position", m.match, function (s, i) return i - 1 end, s) +checkerr("invalid position", m.match, + m.P(1)^0 * function (_, i) return i - 1 end, s) +assert(m.match(m.P(1)^0 * function (_, i) return i end * -1, s)) +checkerr("invalid position", m.match, + m.P(1)^0 * function (_, i) return i + 1 end, s) +assert(m.match(m.P(function (s, i) return s:len() + 1 end) * -1, s)) +checkerr("invalid position", m.match, m.P(function (s, i) return s:len() + 2 end) * -1, s) +assert(not m.match(m.P(function (s, i) return s:len() end) * -1, s)) +assert(m.match(m.P(1)^0 * function (_, i) return true end, s) == + string.len(s) + 1) +for i = 1, string.len(s) + 1 do + assert(m.match(function (_, _) return i end, s) == i) +end + +p = (m.P(function (s, i) return i%2 == 0 and i end) * 1 + + m.P(function (s, i) return i%2 ~= 0 and i + 2 <= s:len() and i end) * 3)^0 + * -1 +assert(p:match(string.rep('a', 14000))) + +-- tests for Function Replacements +f = function (a, ...) if a ~= "x" then return {a, ...} end end + +t = m.match(m.C(1)^0/f, "abc") +checkeq(t, {"a", "b", "c"}) + +t = m.match(m.C(1)^0/f/f, "abc") +checkeq(t, {{"a", "b", "c"}}) + +t = m.match(m.P(1)^0/f/f, "abc") -- no capture +checkeq(t, {{"abc"}}) + +t = m.match((m.P(1)^0/f * m.Cp())/f, "abc") +checkeq(t, {{"abc"}, 4}) + +t = m.match((m.C(1)^0/f * m.Cp())/f, "abc") +checkeq(t, {{"a", "b", "c"}, 4}) + +t = m.match((m.C(1)^0/f * m.Cp())/f, "xbc") +checkeq(t, {4}) + +t = m.match(m.C(m.C(1)^0)/f, "abc") +checkeq(t, {"abc", "a", "b", "c"}) + +g = function (...) return 1, ... end +t = {m.match(m.C(1)^0/g/g, "abc")} +checkeq(t, {1, 1, "a", "b", "c"}) + +t = {m.match(m.Cc(nil,nil,4) * m.Cc(nil,3) * m.Cc(nil, nil) / g / g, "")} +t1 = {1,1,nil,nil,4,nil,3,nil,nil} +for i=1,10 do assert(t[i] == t1[i]) end + +-- bug in 0.12.2: ktable with only nil could be eliminated when joining +-- with a pattern without ktable +assert((m.P"aaa" * m.Cc(nil)):match"aaa" == nil) + +t = {m.match((m.C(1) / function (x) return x, x.."x" end)^0, "abc")} +checkeq(t, {"a", "ax", "b", "bx", "c", "cx"}) + +t = m.match(m.Ct((m.C(1) / function (x,y) return y, x end * m.Cc(1))^0), "abc") +checkeq(t, {nil, "a", 1, nil, "b", 1, nil, "c", 1}) + +-- tests for Query Replacements + +assert(m.match(m.C(m.C(1)^0)/{abc = 10}, "abc") == 10) +assert(m.match(m.C(1)^0/{a = 10}, "abc") == 10) +assert(m.match(m.S("ba")^0/{ab = 40}, "abc") == 40) +t = m.match(m.Ct((m.S("ba")/{a = 40})^0), "abc") +checkeq(t, {40}) + +assert(m.match(m.Cs((m.C(1)/{a=".", d=".."})^0), "abcdde") == ".bc....e") +assert(m.match(m.Cs((m.C(1)/{f="."})^0), "abcdde") == "abcdde") +assert(m.match(m.Cs((m.C(1)/{d="."})^0), "abcdde") == "abc..e") +assert(m.match(m.Cs((m.C(1)/{e="."})^0), "abcdde") == "abcdd.") +assert(m.match(m.Cs((m.C(1)/{e=".", f="+"})^0), "eefef") == "..+.+") +assert(m.match(m.Cs((m.C(1))^0), "abcdde") == "abcdde") +assert(m.match(m.Cs(m.C(m.C(1)^0)), "abcdde") == "abcdde") +assert(m.match(1 * m.Cs(m.P(1)^0), "abcdde") == "bcdde") +assert(m.match(m.Cs((m.C('0')/'x' + 1)^0), "abcdde") == "abcdde") +assert(m.match(m.Cs((m.C('0')/'x' + 1)^0), "0ab0b0") == "xabxbx") +assert(m.match(m.Cs((m.C('0')/'x' + m.P(1)/{b=3})^0), "b0a0b") == "3xax3") +assert(m.match(m.P(1)/'%0%0'/{aa = -3} * 'x', 'ax') == -3) +assert(m.match(m.C(1)/'%0%1'/{aa = 'z'}/{z = -3} * 'x', 'ax') == -3) + +assert(m.match(m.Cs(m.Cc(0) * (m.P(1)/"")), "4321") == "0") + +assert(m.match(m.Cs((m.P(1) / "%0")^0), "abcd") == "abcd") +assert(m.match(m.Cs((m.P(1) / "%0.%0")^0), "abcd") == "a.ab.bc.cd.d") +assert(m.match(m.Cs((m.P("a") / "%0.%0" + 1)^0), "abcad") == "a.abca.ad") +assert(m.match(m.C("a") / "%1%%%0", "a") == "a%a") +assert(m.match(m.Cs((m.P(1) / ".xx")^0), "abcd") == ".xx.xx.xx.xx") +assert(m.match(m.Cp() * m.P(3) * m.Cp()/"%2%1%1 - %0 ", "abcde") == + "411 - abc ") + +assert(m.match(m.P(1)/"%0", "abc") == "a") +checkerr("invalid capture index", m.match, m.P(1)/"%1", "abc") +checkerr("invalid capture index", m.match, m.P(1)/"%9", "abc") + +p = m.C(1) +p = p * p; p = p * p; p = p * p * m.C(1) / "%9 - %1" +assert(p:match("1234567890") == "9 - 1") + +assert(m.match(m.Cc(print), "") == print) + +-- too many captures (just ignore extra ones) +p = m.C(1)^0 / "%2-%9-%0-%9" +assert(p:match"01234567890123456789" == "1-8-01234567890123456789-8") +s = string.rep("12345678901234567890", 20) +assert(m.match(m.C(1)^0 / "%9-%1-%0-%3", s) == "9-1-" .. s .. "-3") + +-- string captures with non-string subcaptures +p = m.Cc('alo') * m.C(1) / "%1 - %2 - %1" +assert(p:match'x' == 'alo - x - alo') + +checkerr("invalid capture value (a boolean)", m.match, m.Cc(true) / "%1", "a") + +-- long strings for string capture +l = 10000 +s = string.rep('a', l) .. string.rep('b', l) .. string.rep('c', l) + +p = (m.C(m.P'a'^1) * m.C(m.P'b'^1) * m.C(m.P'c'^1)) / '%3%2%1' + +assert(p:match(s) == string.rep('c', l) .. + string.rep('b', l) .. + string.rep('a', l)) + +print"+" + +-- accumulator capture +function f (x) return x + 1 end +assert(m.match(m.Cf(m.Cc(0) * m.C(1)^0, f), "alo alo") == 7) +assert(m.match(m.Cc(0) * (m.C(1) % f)^0, "alo alo") == 7) + +t = {m.match(m.Cf(m.Cc(1,2,3), error), "")} +checkeq(t, {1}) +p = m.Cf(m.Ct(true) * m.Cg(m.C(m.R"az"^1) * "=" * m.C(m.R"az"^1) * ";")^0, + rawset) +t = p:match("a=b;c=du;xux=yuy;") +checkeq(t, {a="b", c="du", xux="yuy"}) + + +-- errors in fold capture + +-- no initial capture +checkerr("no initial value", m.match, m.Cf(m.P(5), print), 'aaaaaa') +-- no initial capture (very long match forces fold to be a pair open-close) +checkerr("no initial value", m.match, m.Cf(m.P(500), print), + string.rep('a', 600)) + + +-- errors in accumulator capture + +-- no initial capture +checkerr("no previous value", m.match, m.P(5) % print, 'aaaaaa') +-- no initial capture (very long match forces fold to be a pair open-close) +checkerr("no previous value", m.match, m.P(500) % print, + string.rep('a', 600)) + + +-- tests for loop checker + +local function isnullable (p) + checkerr("may accept empty string", function (p) return p^0 end, m.P(p)) +end + +isnullable(m.P("x")^-4) +assert(m.match(((m.P(0) + 1) * m.S"al")^0, "alo") == 3) +assert(m.match((("x" + #m.P(1))^-4 * m.S"al")^0, "alo") == 3) +isnullable("") +isnullable(m.P("x")^0) +isnullable(m.P("x")^-1) +isnullable(m.P("x") + 1 + 2 + m.P("a")^-1) +isnullable(-m.P("ab")) +isnullable(- -m.P("ab")) +isnullable(# #(m.P("ab") + "xy")) +isnullable(- #m.P("ab")^0) +isnullable(# -m.P("ab")^1) +isnullable(#m.V(3)) +isnullable(m.V(3) + m.V(1) + m.P('a')^-1) +isnullable({[1] = m.V(2) * m.V(3), [2] = m.V(3), [3] = m.P(0)}) +assert(m.match(m.P{[1] = m.V(2) * m.V(3), [2] = m.V(3), [3] = m.P(1)}^0, "abc") + == 3) +assert(m.match(m.P""^-3, "a") == 1) + +local function find (p, s) + return m.match(basiclookfor(p), s) +end + + +local function badgrammar (g, expected) + local stat, msg = pcall(m.P, g) + assert(not stat) + if expected then assert(find(expected, msg)) end +end + +badgrammar({[1] = m.V(1)}, "rule '1'") +badgrammar({[1] = m.V(2)}, "rule '2'") -- invalid non-terminal +badgrammar({[1] = m.V"x"}, "rule 'x'") -- invalid non-terminal +badgrammar({[1] = m.V{}}, "rule '(a table)'") -- invalid non-terminal +badgrammar({[1] = #m.P("a") * m.V(1)}, "rule '1'") -- left-recursive +badgrammar({[1] = -m.P("a") * m.V(1)}, "rule '1'") -- left-recursive +badgrammar({[1] = -1 * m.V(1)}, "rule '1'") -- left-recursive +badgrammar({[1] = -1 + m.V(1)}, "rule '1'") -- left-recursive +badgrammar({[1] = 1 * m.V(2), [2] = m.V(2)}, "rule '2'") -- left-recursive +badgrammar({[1] = 1 * m.V(2)^0, [2] = m.P(0)}, "rule '1'") -- inf. loop +badgrammar({ m.V(2), m.V(3)^0, m.P"" }, "rule '2'") -- inf. loop +badgrammar({ m.V(2) * m.V(3)^0, m.V(3)^0, m.P"" }, "rule '1'") -- inf. loop +badgrammar({"x", x = #(m.V(1) * 'a') }, "rule '1'") -- inf. loop +badgrammar({ -(m.V(1) * 'a') }, "rule '1'") -- inf. loop +badgrammar({"x", x = m.P'a'^-1 * m.V"x"}, "rule 'x'") -- left recursive +badgrammar({"x", x = m.P'a' * m.V"y"^1, y = #m.P(1)}, "rule 'x'") + +assert(m.match({'a' * -m.V(1)}, "aaa") == 2) +assert(m.match({'a' * -m.V(1)}, "aaaa") == nil) + + +-- good x bad grammars +m.P{ ('a' * m.V(1))^-1 } +m.P{ -('a' * m.V(1)) } +m.P{ ('abc' * m.V(1))^-1 } +m.P{ -('abc' * m.V(1)) } +badgrammar{ #m.P('abc') * m.V(1) } +badgrammar{ -('a' + m.V(1)) } +m.P{ #('a' * m.V(1)) } +badgrammar{ #('a' + m.V(1)) } +m.P{ m.B{ m.P'abc' } * 'a' * m.V(1) } +badgrammar{ m.B{ m.P'abc' } * m.V(1) } +badgrammar{ ('a' + m.P'bcd')^-1 * m.V(1) } + + +-- simple tests for maximum sizes: +local p = m.P"a" +for i=1,14 do p = p * p end + +p = {} +for i=1,100 do p[i] = m.P"a" end +p = m.P(p) + + +-- strange values for rule labels + +p = m.P{ "print", + print = m.V(print), + [print] = m.V(_G), + [_G] = m.P"a", + } + +assert(p:match("a")) + +-- initial rule +g = {} +for i = 1, 10 do g["i"..i] = "a" * m.V("i"..i+1) end +g.i11 = m.P"" +for i = 1, 10 do + g[1] = "i"..i + local p = m.P(g) + assert(p:match("aaaaaaaaaaa") == 11 - i + 1) +end + + + +print "testing back references" + +checkerr("back reference 'x' not found", m.match, m.Cb('x'), '') +checkerr("back reference 'b' not found", m.match, m.Cg(1, 'a') * m.Cb('b'), 'a') + +p = m.Cg(m.C(1) * m.C(1), "k") * m.Ct(m.Cb("k")) +t = p:match("ab") +checkeq(t, {"a", "b"}) + + +do + -- some basic cases + assert(m.match(m.Cg(m.Cc(3), "a") * m.Cb("a"), "a") == 3) + assert(m.match(m.Cg(m.C(1), 133) * m.Cb(133), "X") == "X") + + -- first reference to 'x' should not see the group enclosing it + local p = m.Cg(m.Cb('x'), 'x') * m.Cb('x') + checkerr("back reference 'x' not found", m.match, p, '') + + local p = m.Cg(m.Cb('x') * m.C(1), 'x') * m.Cb('x') + checkerr("back reference 'x' not found", m.match, p, 'abc') + + -- reference to 'x' should not see the group enclosed in another capture + local s = string.rep("a", 30) + local p = (m.C(1)^-4 * m.Cg(m.C(1), 'x')) / {} * m.Cb('x') + checkerr("back reference 'x' not found", m.match, p, s) + + local p = (m.C(1)^-20 * m.Cg(m.C(1), 'x')) / {} * m.Cb('x') + checkerr("back reference 'x' not found", m.match, p, s) + + -- second reference 'k' should refer to 10 and first ref. 'k' + p = m.Cg(m.Cc(20), 'k') * m.Cg(m.Cc(10) * m.Cb('k') * m.C(1), 'k') + * (m.Cb('k') / function (a,b,c) return a*10 + b + tonumber(c) end) + -- 10 * 10 (Cc) + 20 (Cb) + 7 (C) == 127 + assert(p:match("756") == 127) + +end + +p = m.P(true) +for i = 1, 10 do p = p * m.Cg(1, i) end +for i = 1, 10 do + local p = p * m.Cb(i) + assert(p:match('abcdefghij') == string.sub('abcdefghij', i, i)) +end + + +t = {} +function foo (p) t[#t + 1] = p; return p .. "x" end + +p = m.Cg(m.C(2) / foo, "x") * m.Cb"x" * + m.Cg(m.Cb('x') / foo, "x") * m.Cb"x" * + m.Cg(m.Cb('x') / foo, "x") * m.Cb"x" * + m.Cg(m.Cb('x') / foo, "x") * m.Cb"x" +x = {p:match'ab'} +checkeq(x, {'abx', 'abxx', 'abxxx', 'abxxxx'}) +checkeq(t, {'ab', + 'ab', 'abx', + 'ab', 'abx', 'abxx', + 'ab', 'abx', 'abxx', 'abxxx'}) + + + +-- tests for match-time captures + +p = m.P'a' * (function (s, i) return (s:sub(i, i) == 'b') and i + 1 end) + + 'acd' + +assert(p:match('abc') == 3) +assert(p:match('acd') == 4) + +local function id (s, i, ...) + return true, ... +end + +do -- run-time capture in an end predicate (should discard its value) + local x = 0 + function foo (s, i) + x = x + 1 + return true, x + end + + local p = #(m.Cmt("", foo) * "xx") * m.Cmt("", foo) + assert(p:match("xx") == 2) +end + +assert(m.Cmt(m.Cs((m.Cmt(m.S'abc' / { a = 'x', c = 'y' }, id) + + m.R'09'^1 / string.char + + m.P(1))^0), id):match"acb98+68c" == "xyb\98+\68y") + +p = m.P{'S', + S = m.V'atom' * space + + m.Cmt(m.Ct("(" * space * (m.Cmt(m.V'S'^1, id) + m.P(true)) * ")" * space), id), + atom = m.Cmt(m.C(m.R("AZ", "az", "09")^1), id) +} +x = p:match"(a g () ((b) c) (d (e)))" +checkeq(x, {'a', 'g', {}, {{'b'}, 'c'}, {'d', {'e'}}}); + +x = {(m.Cmt(1, id)^0):match(string.rep('a', 500))} +assert(#x == 500) + +local function id(s, i, x) + if x == 'a' then return i, 1, 3, 7 + else return nil, 2, 4, 6, 8 + end +end + +p = ((m.P(id) * 1 + m.Cmt(2, id) * 1 + m.Cmt(1, id) * 1))^0 +assert(table.concat{p:match('abababab')} == string.rep('137', 4)) + +local function ref (s, i, x) + return m.match(x, s, i - x:len()) +end + +assert(m.Cmt(m.P(1)^0, ref):match('alo') == 4) +assert((m.P(1) * m.Cmt(m.P(1)^0, ref)):match('alo') == 4) +assert(not (m.P(1) * m.Cmt(m.C(1)^0, ref)):match('alo')) + +ref = function (s,i,x) return i == tonumber(x) and i, 'xuxu' end + +assert(m.Cmt(1, ref):match'2') +assert(not m.Cmt(1, ref):match'1') +assert(m.Cmt(m.P(1)^0, ref):match'03') + +function ref (s, i, a, b) + if a == b then return i, a:upper() end +end + +p = m.Cmt(m.C(m.R"az"^1) * "-" * m.C(m.R"az"^1), ref) +p = (any - p)^0 * p * any^0 * -1 + +assert(p:match'abbbc-bc ddaa' == 'BC') + +do -- match-time captures cannot be optimized away + local touch = 0 + f = m.P(function () touch = touch + 1; return true end) + + local function check(n) n = n or 1; assert(touch == n); touch = 0 end + + assert(m.match(f * false + 'b', 'a') == nil); check() + assert(m.match(f * false + 'b', '') == nil); check() + assert(m.match( (f * 'a')^0 * 'b', 'b') == 2); check() + assert(m.match( (f * 'a')^0 * 'b', '') == nil); check() + assert(m.match( (f * 'a')^-1 * 'b', 'b') == 2); check() + assert(m.match( (f * 'a')^-1 * 'b', '') == nil); check() + assert(m.match( ('b' + f * 'a')^-1 * 'b', '') == nil); check() + assert(m.match( (m.P'b'^-1 * f * 'a')^-1 * 'b', '') == nil); check() + assert(m.match( (-m.P(1) * m.P'b'^-1 * f * 'a')^-1 * 'b', '') == nil); + check() + assert(m.match( (f * 'a' + 'b')^-1 * 'b', '') == nil); check() + assert(m.match(f * 'a' + f * 'b', 'b') == 2); check(2) + assert(m.match(f * 'a' + f * 'b', 'a') == 2); check(1) + assert(m.match(-f * 'a' + 'b', 'b') == 2); check(1) + assert(m.match(-f * 'a' + 'b', '') == nil); check(1) +end + +c = '[' * m.Cg(m.P'='^0, "init") * '[' * + { m.Cmt(']' * m.C(m.P'='^0) * ']' * m.Cb("init"), function (_, _, s1, s2) + return s1 == s2 end) + + 1 * m.V(1) } / 0 + +assert(c:match'[==[]]====]]]]==]===[]' == 18) +assert(c:match'[[]=]====]=]]]==]===[]' == 14) +assert(not c:match'[[]=]====]=]=]==]===[]') + + +-- old bug: optimization of concat with fail removed match-time capture +p = m.Cmt(0, function (s) p = s end) * m.P(false) +assert(not p:match('alo')) +assert(p == 'alo') + + +-- ensure that failed match-time captures are not kept on Lua stack +do + local t = {__mode = "kv"}; setmetatable(t,t) + local c = 0 + + local function foo (s,i) + collectgarbage(); + assert(next(t) == "__mode" and next(t, "__mode") == nil) + local x = {} + t[x] = true + c = c + 1 + return i, x + end + + local p = m.P{ m.Cmt(0, foo) * m.P(false) + m.P(1) * m.V(1) + m.P"" } + p:match(string.rep('1', 10)) + assert(c == 11) +end + + +-- Return a match-time capture that returns 'n' captures +local function manyCmt (n) + return m.Cmt("a", function () + local a = {}; for i = 1, n do a[i] = n - i end + return true, unpack(a) + end) +end + +-- bug in 1.0: failed match-time that used previous match-time results +do + local x + local function aux (...) x = #{...}; return false end + local res = {m.match(m.Cmt(manyCmt(20), aux) + manyCmt(10), "a")} + assert(#res == 10 and res[1] == 9 and res[10] == 0) +end + + +-- bug in 1.0: problems with math-times returning too many captures +if _VERSION >= "Lua 5.2" then + local lim = 2^11 - 10 + local res = {m.match(manyCmt(lim), "a")} + assert(#res == lim and res[1] == lim - 1 and res[lim] == 0) + checkerr("too many", m.match, manyCmt(2^15), "a") +end + +p = (m.P(function () return true, "a" end) * 'a' + + m.P(function (s, i) return i, "aa", 20 end) * 'b' + + m.P(function (s,i) if i <= #s then return i, "aaa" end end) * 1)^0 + +t = {p:match('abacc')} +checkeq(t, {'a', 'aa', 20, 'a', 'aaa', 'aaa'}) + + +do print"testing large grammars" + local lim = 1000 -- number of rules + local t = {} + + for i = 3, lim do + t[i] = m.V(i - 1) -- each rule calls previous one + end + t[1] = m.V(lim) -- start on last rule + t[2] = m.C("alo") -- final rule + + local P = m.P(t) -- build grammar + assert(P:match("alo") == "alo") + + t[#t + 1] = m.P("x") -- one more rule... + checkerr("too many rules", m.P, t) +end + + +print "testing UTF-8 ranges" + +do -- a few typical UTF-8 ranges + local p = m.utfR(0x410, 0x44f)^1 / "cyr: %0" + + m.utfR(0x4e00, 0x9fff)^1 / "cjk: %0" + + m.utfR(0x1F600, 0x1F64F)^1 / "emot: %0" + + m.utfR(0, 0x7f)^1 / "ascii: %0" + + m.utfR(0, 0x10ffff) / "other: %0" + + p = m.Ct(p^0) * -m.P(1) + + local cyr = "ждюя" + local emot = "\240\159\152\128\240\159\153\128" -- 😀🙀 + local cjk = "专举乸" + local ascii = "alo" + local last = "\244\143\191\191" -- U+10FFFF + + local s = cyr .. "—" .. emot .. "—" .. cjk .. "—" .. ascii .. last + t = (p:match(s)) + + assert(t[1] == "cyr: " .. cyr and t[2] == "other: —" and + t[3] == "emot: " .. emot and t[4] == "other: —" and + t[5] == "cjk: " .. cjk and t[6] == "other: —" and + t[7] == "ascii: " .. ascii and t[8] == "other: " .. last and + t[9] == nil) + + -- failing UTF-8 matches and borders + assert(not m.match(m.utfR(10, 0x2000), "\9")) + assert(not m.match(m.utfR(10, 0x2000), "\226\128\129")) + assert(m.match(m.utfR(10, 0x2000), "\10") == 2) + assert(m.match(m.utfR(10, 0x2000), "\226\128\128") == 4) +end + + +do -- valid and invalid code points + local p = m.utfR(0, 0x10ffff)^0 + assert(p:match("汉字\128") == #"汉字" + 1) + assert(p:match("\244\159\191") == 1) + assert(p:match("\244\159\191\191") == 1) + assert(p:match("\255") == 1) + + -- basic errors + checkerr("empty range", m.utfR, 1, 0) + checkerr("invalid code point", m.utfR, 1, 0x10ffff + 1) +end + + +do -- back references (fixed width) + -- match a byte after a CJK point + local p = m.B(m.utfR(0x4e00, 0x9fff)) * m.C(1) + p = m.P{ p + m.P(1) * m.V(1) } -- search for 'p' + assert(p:match("ab д 专X x") == "X") + + -- match a byte after a hebrew point + local p = m.B(m.utfR(0x5d0, 0x5ea)) * m.C(1) + p = m.P(#"ש") * p + assert(p:match("שX") == "X") + + checkerr("fixed length", m.B, m.utfR(0, 0x10ffff)) +end + + + +------------------------------------------------------------------- +-- Tests for 're' module +------------------------------------------------------------------- +print"testing 're' module" + +local re = require "re" + +local match, compile = re.match, re.compile + + + +assert(match("a", ".") == 2) +assert(match("a", "''") == 1) +assert(match("", " ! . ") == 1) +assert(not match("a", " ! . ")) +assert(match("abcde", " ( . . ) * ") == 5) +assert(match("abbcde", " [a-c] +") == 5) +assert(match("0abbc1de", "'0' [a-c]+ '1'") == 7) +assert(match("0zz1dda", "'0' [^a-c]+ 'a'") == 8) +assert(match("abbc--", " [a-c] + +") == 5) +assert(match("abbc--", " [ac-] +") == 2) +assert(match("abbc--", " [-acb] + ") == 7) +assert(not match("abbcde", " [b-z] + ")) +assert(match("abb\"de", '"abb"["]"de"') == 7) +assert(match("abceeef", "'ac' ? 'ab' * 'c' { 'e' * } / 'abceeef' ") == "eee") +assert(match("abceeef", "'ac'? 'ab'* 'c' { 'f'+ } / 'abceeef' ") == 8) + +assert(re.match("aaand", "[a]^2") == 3) + +local t = {match("abceefe", "( ( & 'e' {} ) ? . ) * ")} +checkeq(t, {4, 5, 7}) +local t = {match("abceefe", "((&&'e' {})? .)*")} +checkeq(t, {4, 5, 7}) +local t = {match("abceefe", "( ( ! ! 'e' {} ) ? . ) *")} +checkeq(t, {4, 5, 7}) +local t = {match("abceefe", "(( & ! & ! 'e' {})? .)*")} +checkeq(t, {4, 5, 7}) + +assert(match("cccx" , "'ab'? ('ccc' / ('cde' / 'cd'*)? / 'ccc') 'x'+") == 5) +assert(match("cdx" , "'ab'? ('ccc' / ('cde' / 'cd'*)? / 'ccc') 'x'+") == 4) +assert(match("abcdcdx" , "'ab'? ('ccc' / ('cde' / 'cd'*)? / 'ccc') 'x'+") == 8) + +assert(match("abc", "a <- (. a)?") == 4) +b = "balanced <- '(' ([^()] / balanced)* ')'" +assert(match("(abc)", b)) +assert(match("(a(b)((c) (d)))", b)) +assert(not match("(a(b ((c) (d)))", b)) + +b = compile[[ balanced <- "(" ([^()] / balanced)* ")" ]] +assert(b == m.P(b)) +assert(b:match"((((a))(b)))") + +local g = [[ + S <- "0" B / "1" A / "" -- balanced strings + A <- "0" S / "1" A A -- one more 0 + B <- "1" S / "0" B B -- one more 1 +]] +assert(match("00011011", g) == 9) + +local g = [[ + S <- ("0" B / "1" A)* + A <- "0" / "1" A A + B <- "1" / "0" B B +]] +assert(match("00011011", g) == 9) +assert(match("000110110", g) == 9) +assert(match("011110110", g) == 3) +assert(match("000110010", g) == 1) + +s = "aaaaaaaaaaaaaaaaaaaaaaaa" +assert(match(s, "'a'^3") == 4) +assert(match(s, "'a'^0") == 1) +assert(match(s, "'a'^+3") == s:len() + 1) +assert(not match(s, "'a'^+30")) +assert(match(s, "'a'^-30") == s:len() + 1) +assert(match(s, "'a'^-5") == 6) +for i = 1, s:len() do + assert(match(s, string.format("'a'^+%d", i)) >= i + 1) + assert(match(s, string.format("'a'^-%d", i)) <= i + 1) + assert(match(s, string.format("'a'^%d", i)) == i + 1) +end +assert(match("01234567890123456789", "[0-9]^3+") == 19) + + +assert(match("01234567890123456789", "({....}{...}) -> '%2%1'") == "4560123") +t = match("0123456789", "{| {.}* |}") +checkeq(t, {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}) +assert(match("012345", "{| (..) -> '%0%0' |}")[1] == "0101") + +assert(match("abcdef", "( {.} {.} {.} {.} {.} ) -> 3") == "c") +assert(match("abcdef", "( {:x: . :} {.} {.} {.} {.} ) -> 3") == "d") +assert(match("abcdef", "( {:x: . :} {.} {.} {.} {.} ) -> 0") == 6) + +assert(not match("abcdef", "{:x: ({.} {.} {.}) -> 2 :} =x")) +assert(match("abcbef", "{:x: ({.} {.} {.}) -> 2 :} =x")) + +eqcharset(compile"[]]", "]") +eqcharset(compile"[][]", m.S"[]") +eqcharset(compile"[]-]", m.S"-]") +eqcharset(compile"[-]", m.S"-") +eqcharset(compile"[az-]", m.S"a-z") +eqcharset(compile"[-az]", m.S"a-z") +eqcharset(compile"[a-z]", m.R"az") +eqcharset(compile"[]['\"]", m.S[[]['"]]) + +eqcharset(compile"[^]]", any - "]") +eqcharset(compile"[^][]", any - m.S"[]") +eqcharset(compile"[^]-]", any - m.S"-]") +eqcharset(compile"[^]-]", any - m.S"-]") +eqcharset(compile"[^-]", any - m.S"-") +eqcharset(compile"[^az-]", any - m.S"a-z") +eqcharset(compile"[^-az]", any - m.S"a-z") +eqcharset(compile"[^a-z]", any - m.R"az") +eqcharset(compile"[^]['\"]", any - m.S[[]['"]]) + +-- tests for comments in 're' +e = compile[[ +A <- _B -- \t \n %nl .<> <- -> -- +_B <- 'x' --]] +assert(e:match'xy' == 2) + +-- tests for 're' with pre-definitions +defs = {digits = m.R"09", letters = m.R"az", _=m.P"__"} +e = compile("%letters (%letters / %digits)*", defs) +assert(e:match"x123" == 5) +e = compile("%_", defs) +assert(e:match"__" == 3) + +e = compile([[ + S <- A+ + A <- %letters+ B + B <- %digits+ +]], defs) + +e = compile("{[0-9]+'.'?[0-9]*} -> sin", math) +assert(e:match("2.34") == math.sin(2.34)) + +e = compile("'pi' -> math", _G) +assert(e:match("pi") == math.pi) + +e = compile("[ ]* 'version' -> _VERSION", _G) +assert(e:match(" version") == _VERSION) + + +function eq (_, _, a, b) return a == b end + +c = re.compile([[ + longstring <- '[' {:init: '='* :} '[' close + close <- ']' =init ']' / . close +]]) + +assert(c:match'[==[]]===]]]]==]===[]' == 17) +assert(c:match'[[]=]====]=]]]==]===[]' == 14) +assert(not c:match'[[]=]====]=]=]==]===[]') + +c = re.compile" '[' {:init: '='* :} '[' (!(']' =init ']') .)* ']' =init ']' !. " + +assert(c:match'[==[]]===]]]]==]') +assert(c:match'[[]=]====]=][]==]===[]]') +assert(not c:match'[[]=]====]=]=]==]===[]') + +assert(re.find("hi alalo", "{:x:..:} =x") == 4) +assert(re.find("hi alalo", "{:x:..:} =x", 4) == 4) +assert(not re.find("hi alalo", "{:x:..:} =x", 5)) +assert(re.find("hi alalo", "{'al'}", 5) == 6) +assert(re.find("hi aloalolo", "{:x:..:} =x") == 8) +assert(re.find("alo alohi x x", "{:word:%w+:}%W*(=word)!%w") == 11) + +-- re.find discards any captures +local a,b,c = re.find("alo", "{.}{'o'}") +assert(a == 2 and b == 3 and c == nil) + +local function match (s,p) + local i,e = re.find(s,p) + if i then return s:sub(i, e) end +end +assert(match("alo alo", '[a-z]+') == "alo") +assert(match("alo alo", '{:x: [a-z]+ :} =x') == nil) +assert(match("alo alo", "{:x: [a-z]+ :} ' ' =x") == "alo alo") + +assert(re.gsub("alo alo", "[abc]", "x") == "xlo xlo") +assert(re.gsub("alo alo", "%w+", ".") == ". .") +assert(re.gsub("hi, how are you", "[aeiou]", string.upper) == + "hI, hOw ArE yOU") + +s = 'hi [[a comment[=]=] ending here]] and [=[another]]=]]' +c = re.compile" '[' {:i: '='* :} '[' (!(']' =i ']') .)* ']' { =i } ']' " +assert(re.gsub(s, c, "%2") == 'hi and =]') +assert(re.gsub(s, c, "%0") == s) +assert(re.gsub('[=[hi]=]', c, "%2") == '=') + +assert(re.find("", "!.") == 1) +assert(re.find("alo", "!.") == 4) + +function addtag (s, i, t, tag) t.tag = tag; return i, t end + +c = re.compile([[ + doc <- block !. + block <- (start {| (block / { [^<]+ })* |} end?) => addtag + start <- '<' {:tag: [a-z]+ :} '>' + end <- '' +]], {addtag = addtag}) + +x = c:match[[ +hihellobuttotheend]] +checkeq(x, {tag='x', 'hi', {tag = 'b', 'hello'}, 'but', + {'totheend'}}) + + +-- test for folding captures +c = re.compile([[ + S <- (number (%s+ number)*) ~> add + number <- %d+ -> tonumber +]], {tonumber = tonumber, add = function (a,b) return a + b end}) +assert(c:match("3 401 50") == 3 + 401 + 50) + +-- test for accumulator captures +c = re.compile([[ + S <- number (%s+ number >> add)* + number <- %d+ -> tonumber +]], {tonumber = tonumber, add = function (a,b) return a + b end}) +assert(c:match("3 401 50") == 3 + 401 + 50) + +-- tests for look-ahead captures +x = {re.match("alo", "&(&{.}) !{'b'} {&(...)} &{..} {...} {!.}")} +checkeq(x, {"", "alo", ""}) + +assert(re.match("aloalo", + "{~ (((&'al' {.}) -> 'A%1' / (&%l {.}) -> '%1%1') / .)* ~}") + == "AallooAalloo") + +-- bug in 0.9 (and older versions), due to captures in look-aheads +x = re.compile[[ {~ (&(. ([a-z]* -> '*')) ([a-z]+ -> '+') ' '*)* ~} ]] +assert(x:match"alo alo" == "+ +") + +-- valid capture in look-ahead (used inside the look-ahead itself) +x = re.compile[[ + S <- &({:two: .. :} . =two) {[a-z]+} / . S +]] +assert(x:match("hello aloaLo aloalo xuxu") == "aloalo") + + +p = re.compile[[ + block <- {| {:ident:space*:} line + ((=ident !space line) / &(=ident space) block)* |} + line <- {[^%nl]*} %nl + space <- '_' -- should be ' ', but '_' is simpler for editors +]] + +t= p:match[[ +1 +__1.1 +__1.2 +____1.2.1 +____ +2 +__2.1 +]] +checkeq(t, {"1", {"1.1", "1.2", {"1.2.1", "", ident = "____"}, ident = "__"}, + "2", {"2.1", ident = "__"}, ident = ""}) + + +-- nested grammars +p = re.compile[[ + s <- a b !. + b <- ( x <- ('b' x)? ) + a <- ( x <- 'a' x? ) +]] + +assert(p:match'aaabbb') +assert(p:match'aaa') +assert(not p:match'bbb') +assert(not p:match'aaabbba') + +-- testing groups +t = {re.match("abc", "{:S <- {:.:} {S} / '':}")} +checkeq(t, {"a", "bc", "b", "c", "c", ""}) + +t = re.match("1234", "{| {:a:.:} {:b:.:} {:c:.{.}:} |}") +checkeq(t, {a="1", b="2", c="4"}) +t = re.match("1234", "{|{:a:.:} {:b:{.}{.}:} {:c:{.}:}|}") +checkeq(t, {a="1", b="2", c="4"}) +t = re.match("12345", "{| {:.:} {:b:{.}{.}:} {:{.}{.}:} |}") +checkeq(t, {"1", b="2", "4", "5"}) +t = re.match("12345", "{| {:.:} {:{:b:{.}{.}:}:} {:{.}{.}:} |}") +checkeq(t, {"1", "23", "4", "5"}) +t = re.match("12345", "{| {:.:} {{:b:{.}{.}:}} {:{.}{.}:} |}") +checkeq(t, {"1", "23", "4", "5"}) + + +-- testing pre-defined names +assert(os.setlocale("C") == "C") + +function eqlpeggsub (p1, p2) + local s1 = cs2str(re.compile(p1)) + local s2 = string.gsub(allchar, "[^" .. p2 .. "]", "") + -- if s1 ~= s2 then print(#s1,#s2) end + assert(s1 == s2) +end + + +eqlpeggsub("%w", "%w") +eqlpeggsub("%a", "%a") +eqlpeggsub("%l", "%l") +eqlpeggsub("%u", "%u") +eqlpeggsub("%p", "%p") +eqlpeggsub("%d", "%d") +eqlpeggsub("%x", "%x") +eqlpeggsub("%s", "%s") +eqlpeggsub("%c", "%c") + +eqlpeggsub("%W", "%W") +eqlpeggsub("%A", "%A") +eqlpeggsub("%L", "%L") +eqlpeggsub("%U", "%U") +eqlpeggsub("%P", "%P") +eqlpeggsub("%D", "%D") +eqlpeggsub("%X", "%X") +eqlpeggsub("%S", "%S") +eqlpeggsub("%C", "%C") + +eqlpeggsub("[%w]", "%w") +eqlpeggsub("[_%w]", "_%w") +eqlpeggsub("[^%w]", "%W") +eqlpeggsub("[%W%S]", "%W%S") + +re.updatelocale() + + +-- testing nested substitutions x string captures + +p = re.compile[[ + text <- {~ item* ~} + item <- macro / [^()] / '(' item* ')' + arg <- ' '* {~ (!',' item)* ~} + args <- '(' arg (',' arg)* ')' + macro <- ('apply' args) -> '%1(%2)' + / ('add' args) -> '%1 + %2' + / ('mul' args) -> '%1 * %2' +]] + +assert(p:match"add(mul(a,b), apply(f,x))" == "a * b + f(x)") + +rev = re.compile[[ R <- (!.) -> '' / ({.} R) -> '%2%1']] + +assert(rev:match"0123456789" == "9876543210") + + +-- testing error messages in re + +local function errmsg (p, err) + checkerr(err, re.compile, p) +end + +errmsg('aaaa', "rule 'aaaa'") +errmsg('a', 'outside') +errmsg('b <- a', 'undefined') +errmsg("x <- 'a' x <- 'b'", 'already defined') +errmsg("'a' -", "near '-'") + + +print"OK" + + diff --git a/third_party/luafilesystem b/third_party/luafilesystem new file mode 160000 index 000000000..912e06714 --- /dev/null +++ b/third_party/luafilesystem @@ -0,0 +1 @@ +Subproject commit 912e06714fc276c15b4d5d1b42bd2b11edb8deff diff --git a/third_party/pprint.lua/pprint-internals.lua b/third_party/pprint.lua/pprint-internals.lua new file mode 100644 index 000000000..b84acc603 --- /dev/null +++ b/third_party/pprint.lua/pprint-internals.lua @@ -0,0 +1,127 @@ +--lualoader, R"EOF(-- + +-- seems this is the only way to escape these, as lua don't know how to map char '\a' to 'a' +local ESCAPE_MAP = { + ['\a'] = '\\a', ['\b'] = '\\b', ['\f'] = '\\f', ['\n'] = '\\n', ['\r'] = '\\r', + ['\t'] = '\\t', ['\v'] = '\\v', ['\\'] = '\\\\', +} + +-- generic utilities +pprint._internals.tokenize_string = function(s) + local t = {} + for i = 1, #s do + local c = s:sub(i, i) + local b = c:byte() + local e = ESCAPE_MAP[c] + if (b >= 0x20 and b < 0x80) or e then + local s = e or c + t[i] = { char = s, len = #s } + else + t[i] = { char = string.format('\\x%02x', b), len = 4 } + end + if c == '"' then + t.has_double_quote = true + elseif c == "'" then + t.has_single_quote = true + end + end + return t +end +pprint._internals.tokenize_utf8_string = tokenize_string + +local has_lpeg, lpeg = pcall(require, 'lpeg') + +if has_lpeg then + local function utf8_valid_char(c) + return { char = c, len = 1 } + end + + local function utf8_invalid_char(c) + local b = c:byte() + local e = ESCAPE_MAP[c] + if (b >= 0x20 and b < 0x80) or e then + local s = e or c + return { char = s, len = #s } + else + return { char = string.format('\\x%02x', b), len = 4 } + end + end + + local cont = lpeg.R('\x80\xbf') + local utf8_char = + lpeg.R('\x20\x7f') + + lpeg.R('\xc0\xdf') * cont + + lpeg.R('\xe0\xef') * cont * cont + + lpeg.R('\xf0\xf7') * cont * cont * cont + + local utf8_capture = (((utf8_char / utf8_valid_char) + (lpeg.P(1) / utf8_invalid_char)) ^ 0) * -1 + + pprint._internals.tokenize_utf8_string = function(s) + local dq = s:find('"') + local sq = s:find("'") + local t = table.pack(utf8_capture:match(s)) + t.has_double_quote = not not dq + t.has_single_quote = not not sq + return t + end +end + +local CACHE_TYPES = { + ['table'] = true, ['function'] = true, ['thread'] = true, + ['userdata'] = true, ['cdata'] = true, +} + +-- cache would be populated to be like: +-- { +-- function = { `fun1` = 1, _cnt = 1 }, -- object id +-- table = { `table1` = 1, `table2` = 2, _cnt = 2 }, +-- visited_tables = { `table1` = 7, `table2` = 8 }, -- visit count +-- } +-- use weakrefs to avoid accidentall adding refcount +pprint._internals.cache_apperance = function(obj, cache, option) + if not cache.visited_tables then + cache.visited_tables = setmetatable({}, {__mode = 'k'}) + end + local t = type(obj) + + -- TODO can't test filter_function here as we don't have the ix and key, + -- might cause different results? + -- respect show_xxx and filter_function to be consistent with print results + if (not pprint._internals.TYPES[t] and not option.show_table) + or (pprint._internals.TYPES[t] and not option['show_'..t]) then + return + end + + if CACHE_TYPES[t] or pprint._internals.TYPES[t] == nil then + if not cache[t] then + cache[t] = setmetatable({}, {__mode = 'k'}) + cache[t]._cnt = 0 + end + if not cache[t][obj] then + cache[t]._cnt = cache[t]._cnt + 1 + cache[t][obj] = cache[t]._cnt + end + end + if t == 'table' or pprint._internals.TYPES[t] == nil then + if cache.visited_tables[obj] == false then + -- already printed, no need to mark this and its children anymore + return + elseif cache.visited_tables[obj] == nil then + cache.visited_tables[obj] = 1 + else + -- visited already, increment and continue + cache.visited_tables[obj] = cache.visited_tables[obj] + 1 + return + end + for k, v in pairs(obj) do + pprint._internals.cache_apperance(k, cache, option) + pprint._internals.cache_apperance(v, cache, option) + end + local mt = getmetatable(obj) + if mt and option.show_metatable then + pprint._internals.cache_apperance(mt, cache, option) + end + end +end + +-- )EOF" diff --git a/third_party/pprint.lua/pprint.lua b/third_party/pprint.lua/pprint.lua index f879db6b0..f5820c43e 100644 --- a/third_party/pprint.lua/pprint.lua +++ b/third_party/pprint.lua/pprint.lua @@ -4,6 +4,8 @@ pprint = { VERSION = '0.1' } local depth = 1 +pprint._internals = {} + pprint.defaults = { -- If set to number N, then limit table recursion to N deep. depth_limit = false, @@ -17,7 +19,7 @@ pprint.defaults = { show_function = false, show_thread = false, show_userdata = false, - show_cdata = false, + show_cdata = false, -- additional display trigger show_metatable = false, -- show metatable show_all = false, -- override other show settings and show everything @@ -30,97 +32,20 @@ pprint.defaults = { level_width = 80, -- max width per indent level wrap_string = true, -- wrap string when it's longer than level_width wrap_array = false, -- wrap every array elements + string_is_utf8 = true, -- treat string as utf8, and count utf8 char when wrapping, if possible sort_keys = true, -- sort table keys } -local TYPES = { +pprint._internals.TYPES = { ['nil'] = 1, ['boolean'] = 2, ['number'] = 3, ['string'] = 4, ['table'] = 5, ['function'] = 6, ['thread'] = 7, ['userdata'] = 8, ['cdata'] = 9, } --- seems this is the only way to escape these, as lua don't know how to map char '\a' to 'a' -local ESCAPE_MAP = { - ['\a'] = '\\a', ['\b'] = '\\b', ['\f'] = '\\f', ['\n'] = '\\n', ['\r'] = '\\r', - ['\t'] = '\\t', ['\v'] = '\\v', ['\\'] = '\\\\', -} - --- generic utilities -local function escape(s) - s = s:gsub('([%c\\])', ESCAPE_MAP) - local dq = s:find('"') - local sq = s:find("'") - if dq and sq then - return s:gsub('"', '\\"'), '"' - elseif sq then - return s, '"' - else - return s, "'" - end -end - local function is_plain_key(key) return type(key) == 'string' and key:match('^[%a_][%a%d_]*$') end -local CACHE_TYPES = { - ['table'] = true, ['function'] = true, ['thread'] = true, - ['userdata'] = true, ['cdata'] = true, -} - --- cache would be populated to be like: --- { --- function = { `fun1` = 1, _cnt = 1 }, -- object id --- table = { `table1` = 1, `table2` = 2, _cnt = 2 }, --- visited_tables = { `table1` = 7, `table2` = 8 }, -- visit count --- } --- use weakrefs to avoid accidentall adding refcount -local function cache_apperance(obj, cache, option) - if not cache.visited_tables then - cache.visited_tables = setmetatable({}, {__mode = 'k'}) - end - local t = type(obj) - - -- TODO can't test filter_function here as we don't have the ix and key, - -- might cause different results? - -- respect show_xxx and filter_function to be consistent with print results - if (not TYPES[t] and not option.show_table) - or (TYPES[t] and not option['show_'..t]) then - return - end - - if CACHE_TYPES[t] or TYPES[t] == nil then - if not cache[t] then - cache[t] = setmetatable({}, {__mode = 'k'}) - cache[t]._cnt = 0 - end - if not cache[t][obj] then - cache[t]._cnt = cache[t]._cnt + 1 - cache[t][obj] = cache[t]._cnt - end - end - if t == 'table' or TYPES[t] == nil then - if cache.visited_tables[obj] == false then - -- already printed, no need to mark this and its children anymore - return - elseif cache.visited_tables[obj] == nil then - cache.visited_tables[obj] = 1 - else - -- visited already, increment and continue - cache.visited_tables[obj] = cache.visited_tables[obj] + 1 - return - end - for k, v in pairs(obj) do - cache_apperance(k, cache, option) - cache_apperance(v, cache, option) - end - local mt = getmetatable(obj) - if mt and option.show_metatable then - cache_apperance(mt, cache, option) - end - end -end - -- makes 'foo2' < 'foo100000'. string.sub makes substring anyway, no need to use index based method local function str_natural_cmp(lhs, rhs) while #lhs > 0 and #rhs > 0 do @@ -154,8 +79,8 @@ local function cmp(lhs, rhs) if tleft == tright then return str_natural_cmp(tostring(lhs), tostring(rhs)) end -- allow custom types - local oleft = TYPES[tleft] or 10 - local oright = TYPES[tright] or 10 + local oleft = pprint._internals.TYPES[tleft] or 10 + local oright = pprint._internals.TYPES[tright] or 10 return oleft < oright end @@ -169,7 +94,7 @@ local function make_option(option) option[k] = v end if option.show_all then - for t, _ in pairs(TYPES) do + for t, _ in pairs(pprint._internals.TYPES) do option['show_'..t] = true end option.show_metatable = true @@ -206,9 +131,11 @@ function pprint.pformat(obj, option, printer) local status = { indent = '', -- current indent len = 0, -- current line length + printed_something = false, -- used to remove leading new lines } local wrapped_printer = function(s) + status.printed_something = true printer(last) last = s end @@ -218,6 +145,7 @@ function pprint.pformat(obj, option, printer) end local function _n(d) + if not status.printed_something then return end wrapped_printer('\n') wrapped_printer(status.indent) if d then @@ -274,26 +202,85 @@ function pprint.pformat(obj, option, printer) end local function string_formatter(s, force_long_quote) - local s, quote = escape(s) - local quote_len = force_long_quote and 4 or 2 - if quote_len + #s + status.len > option.level_width then + local tokens = option.string_is_utf8 and pprint._internals.tokenize_utf8_string(s) or pprint._internals.tokenize_string(s) + local string_len = 0 + local escape_quotes = tokens.has_double_quote and tokens.has_single_quote + for _, token in ipairs(tokens) do + if escape_quotes and token.char == '"' then + string_len = string_len + 2 + else + string_len = string_len + token.len + end + end + local quote_len = 2 + local long_quote_dashes = 0 + local function compute_long_quote_dashes() + local keep_looking = true + while keep_looking do + if s:find('%]' .. string.rep('=', long_quote_dashes) .. '%]') then + long_quote_dashes = long_quote_dashes + 1 + else + keep_looking = false + end + end + end + if force_long_quote then + compute_long_quote_dashes() + quote_len = 2 + long_quote_dashes + end + if quote_len + string_len + status.len > option.level_width then _n() -- only wrap string when is longer than level_width - if option.wrap_string and #s + quote_len > option.level_width then + if option.wrap_string and string_len + quote_len > option.level_width then + if not force_long_quote then + compute_long_quote_dashes() + quote_len = 2 + long_quote_dashes + end -- keep the quotes together - _p('[[') - while #s + status.len >= option.level_width do - local seg = option.level_width - status.len - _p(string.sub(s, 1, seg), true) - _n() - s = string.sub(s, seg+1) + local dashes = string.rep('=', long_quote_dashes) + _p('[' .. dashes .. '[', true) + local status_len = status.len + local line_len = 0 + local line = '' + for _, token in ipairs(tokens) do + if line_len + token.len + status_len > option.level_width then + _n() + _p(line, true) + line_len = token.len + line = token.char + else + line_len = line_len + token.len + line = line .. token.char + end end - _p(s) -- print the remaining parts - return ']]' + + return line .. ']' .. dashes .. ']' end end - return force_long_quote and '[['..s..']]' or quote..s..quote + if tokens.has_double_quote and tokens.has_single_quote and not force_long_quote then + for i, token in ipairs(tokens) do + if token.char == '"' then + tokens[i].char = '\\"' + end + end + end + local flat_table = {} + for _, token in ipairs(tokens) do + table.insert(flat_table, token.char) + end + local concat = table.concat(flat_table) + + if force_long_quote then + local dashes = string.rep('=', long_quote_dashes) + return '[' .. dashes .. '[' .. concat .. ']' .. dashes .. ']' + elseif tokens.has_single_quote then + -- use double quote + return '"' .. concat .. '"' + else + -- use single quote + return "'" .. concat .. "'" + end end local function table_formatter(t) @@ -446,7 +433,7 @@ function pprint.pformat(obj, option, printer) if option.object_cache then -- needs to visit the table before start printing - cache_apperance(obj, cache, option) + pprint._internals.cache_apperance(obj, cache, option) end _p(format(obj)) diff --git a/tools/ghidra_scripts/export_to_redux.py b/tools/ghidra_scripts/export_to_redux.py index a6bef6114..5640e36f5 100644 --- a/tools/ghidra_scripts/export_to_redux.py +++ b/tools/ghidra_scripts/export_to_redux.py @@ -15,6 +15,8 @@ type_name = component.getDataType().getName() print('type: ' + type_name) field_name = component.getFieldName() + if field_name is None: + field_name = component.getDefaultFieldName() print('field name: ' + field_name) field_length = str(component.getLength()) print('field length: ' + field_length) diff --git a/tools/linux-mips/spawn-compiler.sh b/tools/linux-mips/spawn-compiler.sh index 6a98e93ee..1f1a711b9 100755 --- a/tools/linux-mips/spawn-compiler.sh +++ b/tools/linux-mips/spawn-compiler.sh @@ -13,17 +13,17 @@ set -ex PREFIX=${PREFIX:-"/usr/local"} -wget https://ftp.gnu.org/gnu/binutils/binutils-2.40.tar.gz -tar xvfz binutils-2.40.tar.gz -cd binutils-2.40 +wget https://ftp.gnu.org/gnu/binutils/binutils-2.41.tar.gz +tar xvfz binutils-2.41.tar.gz +cd binutils-2.41 ./configure --target=mipsel-none-elf --disable-multilib --disable-nls --disable-werror --prefix=$PREFIX make make install-strip cd .. -wget https://ftp.gnu.org/gnu/gcc/gcc-13.1.0/gcc-13.1.0.tar.gz -tar xvfz gcc-13.1.0.tar.gz -cd gcc-13.1.0 +wget https://ftp.gnu.org/gnu/gcc/gcc-13.2.0/gcc-13.2.0.tar.gz +tar xvfz gcc-13.2.0.tar.gz +cd gcc-13.2.0 ./contrib/download_prerequisites mkdir build cd build diff --git a/tools/macos-mips/mipsel-none-elf-binutils.rb b/tools/macos-mips/mipsel-none-elf-binutils.rb index bdeb05e3d..f4d2c6f78 100644 --- a/tools/macos-mips/mipsel-none-elf-binutils.rb +++ b/tools/macos-mips/mipsel-none-elf-binutils.rb @@ -1,11 +1,11 @@ class MipselNoneElfBinutils < Formula desc "FSF Binutils for mipsel cross development" homepage "https://www.gnu.org/software/binutils/" - url "https://ftp.gnu.org/gnu/binutils/binutils-2.40.tar.gz" - sha256 "d7f82c4047decf43a6f769ac32456a92ddb6932409a585c633cdd4e9df23d956" - + url "https://ftp.gnu.org/gnu/binutils/binutils-2.41.tar.gz" + sha256 "48d00a8dc73aa7d2394a7dc069b96191d95e8de8f0da6dc91da5cce655c20e45" + depends_on "texinfo" => :build - + def install system "./configure", "--target=mipsel-none-elf", "--disable-multilib", diff --git a/tools/macos-mips/mipsel-none-elf-gcc.rb b/tools/macos-mips/mipsel-none-elf-gcc.rb index eb0aca76e..0198c8cd8 100644 --- a/tools/macos-mips/mipsel-none-elf-gcc.rb +++ b/tools/macos-mips/mipsel-none-elf-gcc.rb @@ -1,8 +1,8 @@ class MipselNoneElfGcc < Formula desc "The GNU compiler collection for mipsel" homepage "https://gcc.gnu.org" - url "https://ftp.gnu.org/gnu/gcc/gcc-13.1.0/gcc-13.1.0.tar.xz" - sha256 "61d684f0aa5e76ac6585ad8898a2427aade8979ed5e7f85492286c4dfc13ee86" + url "https://ftp.gnu.org/gnu/gcc/gcc-13.2.0/gcc-13.2.0.tar.xz" + sha256 "e275e76442a6067341a27f04c5c6b83d8613144004c0413528863dc6b5c743da" depends_on "gmp" depends_on "mipsel-none-elf-binutils" diff --git a/tools/vscode-extension/README.md b/tools/vscode-extension/README.md index 29c72415d..b5d1d2cc0 100644 --- a/tools/vscode-extension/README.md +++ b/tools/vscode-extension/README.md @@ -19,6 +19,17 @@ The panel will have the ability to install the tools on the most popular platfor ### Changelog +- 0.2.8 + - Adding main page button to update submodules. + - Creating categories for templates. +- 0.2.7 + - Adding c_cpp_properties.json to the templates. + - Bumping Windows mips toolchain to 13.2.0. +- 0.2.6 + - Bumping gcc to 13.2.0. + - Bumping binutils to 2.41 + - Bumping gdb to 13.2.0 + - Preventing creating projects with spaces in the path. - 0.2.5 - Bumping gcc to 13.1.0. - 0.2.4 diff --git a/tools/vscode-extension/extension.js b/tools/vscode-extension/extension.js index b2709d4a3..5fcbafcd9 100644 --- a/tools/vscode-extension/extension.js +++ b/tools/vscode-extension/extension.js @@ -15,7 +15,7 @@ let globalStorageUri class PSXDevPanel { static currentPanel = undefined - static createOrShow(extensionUri) { + static createOrShow (extensionUri) { const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined @@ -34,11 +34,11 @@ class PSXDevPanel { PSXDevPanel.currentPanel = new PSXDevPanel(panel, extensionUri) } - static revive(panel, extensionUri) { + static revive (panel, extensionUri) { PSXDevPanel.currentPanel = new PSXDevPanel(panel, extensionUri) } - constructor(panel, extensionUri) { + constructor (panel, extensionUri) { this._disposables = [] this._panel = panel this._extensionUri = extensionUri @@ -109,6 +109,9 @@ class PSXDevPanel { case 'restorePsyq': restorePsyq() break + case 'updateModules': + updateModules() + break case 'requestHomeDirectory': this._panel.webview.postMessage({ command: 'projectDirectory', @@ -156,7 +159,7 @@ class PSXDevPanel { ) } - dispose() { + dispose () { PSXDevPanel.currentPanel = undefined // Clean up our resources this._panel.dispose() @@ -168,12 +171,12 @@ class PSXDevPanel { } } - _update() { + _update () { const webview = this._panel.webview webview.html = this._getHtmlForWebview(webview) } - _getHtmlForWebview(webview) { + _getHtmlForWebview (webview) { const scriptPathOnDisk = vscode.Uri.joinPath( this._extensionUri, 'media', @@ -218,6 +221,9 @@ class PSXDevPanel {

After cloning a project that uses the Psy-Q library, it'll be necessary to restore it. You can press the button below in order to restore the library into the current workspace.


Restore Psy-Q

+

The templates will create git repositories with submodules. These submodules may update frequently with bug fixes and new features. The updates should not break backward compatibility in general, and should be safe to do. Press the button below to update the submodules in the current workspace.


+ Update modules
+
@@ -280,26 +286,26 @@ exports.activate = (context) => { ) context.subscriptions.push( - vscode.commands.registerCommand('psxDev.showReduxSettings', () => { - showReduxSettings() + vscode.commands.registerCommand('psxDev.updateModules', () => { + updateModules() }) ) context.subscriptions.push( - vscode.commands.registerCommand('psxDev.updateModules', () => { - updateModules() + vscode.commands.registerCommand('psxDev.showReduxSettings', () => { + showReduxSettings() }) ) vscode.window.registerWebviewPanelSerializer(PSXDevPanel.viewType, { - async deserializeWebviewPanel(webviewPanel) { + async deserializeWebviewPanel (webviewPanel) { webviewPanel.webview.options = getWebviewOptions(context.extensionUri) PSXDevPanel.revive(webviewPanel, context.extensionUri) } }) } -function getWebviewOptions(extensionUri) { +function getWebviewOptions (extensionUri) { return { enableScripts: true, localResourceRoots: [ @@ -315,7 +321,7 @@ function getWebviewOptions(extensionUri) { } } -function getNonce() { +function getNonce () { let text = '' const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' @@ -325,7 +331,7 @@ function getNonce() { return text } -function launchRedux() { +function launchRedux () { tools .maybeInstall('redux') .then(() => tools.list.redux.launch()) @@ -334,7 +340,7 @@ function launchRedux() { }) } -function restorePsyq() { +function restorePsyq () { if (vscode.workspace.workspaceFolders) { tools .maybeInstall('psyq') @@ -355,7 +361,7 @@ function restorePsyq() { } } -function showReduxSettings() { +function showReduxSettings () { const pathToOpen = vscode.Uri.joinPath( globalStorageUri, 'pcsx-redux-settings' @@ -365,7 +371,7 @@ function showReduxSettings() { }) } -function updateModules() { +function updateModules () { if (vscode.workspace.workspaceFolders) { tools .maybeInstall('git') diff --git a/tools/vscode-extension/media/main.js b/tools/vscode-extension/media/main.js index 4d1ef2b48..d97b9e62b 100644 --- a/tools/vscode-extension/media/main.js +++ b/tools/vscode-extension/media/main.js @@ -16,7 +16,7 @@ import { let tools = {} let templates - function checkRequiredTools(templateKey) { + function checkRequiredTools (templateKey) { for (const tool of templates[templateKey].requiredTools) { if (!tools[tool] || !tools[tool].installed) { return false @@ -25,7 +25,7 @@ import { return true } - function refreshTemplates() { + function refreshTemplates () { for (const key of Object.keys(templates)) { const templateCreate = document.getElementById('create-' + key) if (templateCreate) templateCreate.disabled = !checkRequiredTools(key) @@ -71,89 +71,111 @@ import { templatesDiv.appendChild(browseButton) const hr2 = document.createElement('hr') templatesDiv.appendChild(hr2) + const intro = document.createElement('h3') + intro.textContent = 'Fill in your project details above, then select a template category below, and finally select a template to create a new project.' + templatesDiv.appendChild(intro) + const categories = {} for (const [key, template] of Object.entries(templates)) { - const templateDiv = document.createElement('div') - templateDiv.className = 'template' - const templateName = document.createElement('h3') - templateName.textContent = template.name - templateDiv.appendChild(templateName) - const templateDescription = document.createElement('p') - templateDescription.textContent = template.description - templateDiv.appendChild(templateDescription) - const templateDocumentation = - document.createElement('vscode-button') - templateDocumentation.textContent = 'Documentation' - templateDocumentation.appearance = 'secondary' - if (template.url) { - templateDocumentation.addEventListener('click', () => { - vscode.postMessage({ command: 'openUrl', url: template.url }) - }) - } else { - templateDocumentation.disabled = true + if (!categories[template.category]) { + categories[template.category] = [] } - templateDiv.appendChild(templateDocumentation) - const spaceTextNode0 = document.createTextNode(' ') - templateDiv.appendChild(spaceTextNode0) - const templateExamples = document.createElement('vscode-button') - templateExamples.textContent = 'Examples' - templateExamples.appearance = 'secondary' - if (template.examples) { - templateExamples.addEventListener('click', () => { + categories[template.category].push(key) + } + const panels = document.createElement('vscode-panels') + for (const [category, templateKeys] of Object.entries(categories)) { + const panelTab = document.createElement('vscode-panel-tab') + panelTab.textContent = category + panels.appendChild(panelTab) + } + for (const [category, templateKeys] of Object.entries(categories)) { + const panel = document.createElement('vscode-panel-view') + for (const key of templateKeys) { + const template = templates[key] + const templateDiv = document.createElement('div') + templateDiv.className = 'template' + const templateName = document.createElement('h3') + templateName.textContent = template.name + templateDiv.appendChild(templateName) + const templateDescription = document.createElement('p') + templateDescription.textContent = template.description + templateDiv.appendChild(templateDescription) + const templateDocumentation = + document.createElement('vscode-button') + templateDocumentation.textContent = 'Documentation' + templateDocumentation.appearance = 'secondary' + if (template.url) { + templateDocumentation.addEventListener('click', () => { + vscode.postMessage({ command: 'openUrl', url: template.url }) + }) + } else { + templateDocumentation.disabled = true + } + templateDiv.appendChild(templateDocumentation) + const spaceTextNode0 = document.createTextNode(' ') + templateDiv.appendChild(spaceTextNode0) + const templateExamples = document.createElement('vscode-button') + templateExamples.textContent = 'Examples' + templateExamples.appearance = 'secondary' + if (template.examples) { + templateExamples.addEventListener('click', () => { + vscode.postMessage({ + command: 'openUrl', + url: template.examples + }) + }) + } else { + templateExamples.disabled = true + } + templateDiv.appendChild(templateExamples) + const spaceTextNode1 = document.createTextNode(' ') + templateDiv.appendChild(spaceTextNode1) + const templateInstallRequiredTools = + document.createElement('vscode-button') + templateInstallRequiredTools.textContent = 'Install required tools' + templateInstallRequiredTools.appearance = 'secondary' + templateInstallRequiredTools.addEventListener('click', () => { vscode.postMessage({ - command: 'openUrl', - url: template.examples + command: 'installTools', + tools: template.requiredTools }) }) - } else { - templateExamples.disabled = true - } - templateDiv.appendChild(templateExamples) - const spaceTextNode1 = document.createTextNode(' ') - templateDiv.appendChild(spaceTextNode1) - const templateInstallRequiredTools = - document.createElement('vscode-button') - templateInstallRequiredTools.textContent = 'Install required tools' - templateInstallRequiredTools.appearance = 'secondary' - templateInstallRequiredTools.addEventListener('click', () => { - vscode.postMessage({ - command: 'installTools', - tools: template.requiredTools - }) - }) - templateDiv.appendChild(templateInstallRequiredTools) - const spaceTextNode2 = document.createTextNode(' ') - templateDiv.appendChild(spaceTextNode2) - const templateInstallRecommendedTools = - document.createElement('vscode-button') - templateInstallRecommendedTools.textContent = - 'Install recommended tools' - templateInstallRecommendedTools.appearance = 'secondary' - templateInstallRecommendedTools.addEventListener('click', () => { - vscode.postMessage({ - command: 'installTools', - tools: [...template.recommendedTools, ...template.requiredTools] + templateDiv.appendChild(templateInstallRequiredTools) + const spaceTextNode2 = document.createTextNode(' ') + templateDiv.appendChild(spaceTextNode2) + const templateInstallRecommendedTools = + document.createElement('vscode-button') + templateInstallRecommendedTools.textContent = + 'Install recommended tools' + templateInstallRecommendedTools.appearance = 'secondary' + templateInstallRecommendedTools.addEventListener('click', () => { + vscode.postMessage({ + command: 'installTools', + tools: [...template.recommendedTools, ...template.requiredTools] + }) }) - }) - templateDiv.appendChild(templateInstallRecommendedTools) - const spaceTextNode3 = document.createTextNode(' ') - templateDiv.appendChild(spaceTextNode3) - const templateCreate = document.createElement('vscode-button') - templateCreate.textContent = 'Create' - templateCreate.id = 'create-' + key - templateCreate.disabled = true - templateCreate.addEventListener('click', () => { - vscode.postMessage({ - command: 'createProjectFromTemplate', - template: key, - path: document.getElementById('project-path').value, - name: document.getElementById('project-name').value + templateDiv.appendChild(templateInstallRecommendedTools) + const spaceTextNode3 = document.createTextNode(' ') + templateDiv.appendChild(spaceTextNode3) + const templateCreate = document.createElement('vscode-button') + templateCreate.textContent = 'Create' + templateCreate.id = 'create-' + key + templateCreate.disabled = true + templateCreate.addEventListener('click', () => { + vscode.postMessage({ + command: 'createProjectFromTemplate', + template: key, + path: document.getElementById('project-path').value, + name: document.getElementById('project-name').value + }) }) - }) - templateDiv.appendChild(templateCreate) - const hr = document.createElement('hr') - templateDiv.appendChild(hr) - templatesDiv.appendChild(templateDiv) + templateDiv.appendChild(templateCreate) + const hr = document.createElement('hr') + templateDiv.appendChild(hr) + panel.appendChild(templateDiv) + } + panels.appendChild(panel) } + templatesDiv.appendChild(panels) templateView.appendChild(templatesDiv) } break @@ -259,5 +281,8 @@ import { document.getElementById('restore-psyq').addEventListener('click', () => { vscode.postMessage({ command: 'restorePsyq' }) }) + document.getElementById('update-modules').addEventListener('click', () => { + vscode.postMessage({ command: 'updateModules' }) + }) }) })() diff --git a/tools/vscode-extension/package.json b/tools/vscode-extension/package.json index 8bca7307f..a05883fe8 100644 --- a/tools/vscode-extension/package.json +++ b/tools/vscode-extension/package.json @@ -2,7 +2,7 @@ "name": "psx-dev", "displayName": "PSX.Dev", "description": "PlayStation 1 development made easy", - "version": "0.2.5", + "version": "0.2.8", "engines": { "vscode": "^1.75.0" }, diff --git a/tools/vscode-extension/scripts/mipsel-none-elf-binutils.rb b/tools/vscode-extension/scripts/mipsel-none-elf-binutils.rb index bdeb05e3d..f4d2c6f78 100644 --- a/tools/vscode-extension/scripts/mipsel-none-elf-binutils.rb +++ b/tools/vscode-extension/scripts/mipsel-none-elf-binutils.rb @@ -1,11 +1,11 @@ class MipselNoneElfBinutils < Formula desc "FSF Binutils for mipsel cross development" homepage "https://www.gnu.org/software/binutils/" - url "https://ftp.gnu.org/gnu/binutils/binutils-2.40.tar.gz" - sha256 "d7f82c4047decf43a6f769ac32456a92ddb6932409a585c633cdd4e9df23d956" - + url "https://ftp.gnu.org/gnu/binutils/binutils-2.41.tar.gz" + sha256 "48d00a8dc73aa7d2394a7dc069b96191d95e8de8f0da6dc91da5cce655c20e45" + depends_on "texinfo" => :build - + def install system "./configure", "--target=mipsel-none-elf", "--disable-multilib", diff --git a/tools/vscode-extension/scripts/mipsel-none-elf-gcc.rb b/tools/vscode-extension/scripts/mipsel-none-elf-gcc.rb index eb0aca76e..0198c8cd8 100644 --- a/tools/vscode-extension/scripts/mipsel-none-elf-gcc.rb +++ b/tools/vscode-extension/scripts/mipsel-none-elf-gcc.rb @@ -1,8 +1,8 @@ class MipselNoneElfGcc < Formula desc "The GNU compiler collection for mipsel" homepage "https://gcc.gnu.org" - url "https://ftp.gnu.org/gnu/gcc/gcc-13.1.0/gcc-13.1.0.tar.xz" - sha256 "61d684f0aa5e76ac6585ad8898a2427aade8979ed5e7f85492286c4dfc13ee86" + url "https://ftp.gnu.org/gnu/gcc/gcc-13.2.0/gcc-13.2.0.tar.xz" + sha256 "e275e76442a6067341a27f04c5c6b83d8613144004c0413528863dc6b5c743da" depends_on "gmp" depends_on "mipsel-none-elf-binutils" diff --git a/tools/vscode-extension/templates.js b/tools/vscode-extension/templates.js index 5e356736f..f5e7deb67 100644 --- a/tools/vscode-extension/templates.js +++ b/tools/vscode-extension/templates.js @@ -9,8 +9,47 @@ const stringify = (obj) => { return JSON.stringify(obj, null, 2) } -async function createSkeleton(fullPath, name, progressReporter) { +async function createSkeleton (fullPath, name, progressReporter) { await fs.mkdirp(path.join(fullPath, '.vscode')) + await fs.writeFile( + path.join(fullPath, '.vscode', 'c_cpp_properties.json'), + stringify({ + configurations: [ + { + compilerPath: + '${env:AppData}\\mips\\mips\\bin\\mipsel-none-elf-gcc.exe', + cStandard: 'c17', + cppStandard: 'c++20', + defines: ['__STDC_HOSTED__ = 0'], + includePath: [ + '${workspaceFolder}/', + '${workspaceFolder}/third_party/nugget', + '${workspaceFolder}/third_party/nugget/third_party/eastl/include', + '${workspaceFolder}/third_party/nugget/third_party/eabase/include/common', + '${env:AppData}/mips/mips/include' + ], + intelliSenseMode: 'gcc-x86', + name: 'Win32' + }, + { + compilerPath: 'mipsel-linux-gnu-gcc', + cStandard: 'c17', + cppStandard: 'c++20', + defines: ['__STDC_HOSTED__ = 0'], + includePath: [ + '${workspaceFolder}/', + '${workspaceFolder}/third_party/nugget', + '${workspaceFolder}/third_party/nugget/third_party/eastl/include', + '${workspaceFolder}/third_party/nugget/third_party/eabase/include/common', + '/usr/mipsel-linux-gnu/include' + ], + intelliSenseMode: 'gcc-x86', + name: 'linux' + } + ], + version: 4 + }) + ) await fs.writeFile( path.join(fullPath, '.vscode', 'launch.json'), stringify({ @@ -127,7 +166,7 @@ Don't forget that you can always open the main PSX.Dev panel by pressing Ctrl+Sh return git } -async function createEmptyProject(fullPath, name, progressReporter) { +async function createEmptyProject (fullPath, name, progressReporter) { const git = await createSkeleton(fullPath, name, progressReporter) await fs.writeFile( path.join(fullPath, 'main.c'), @@ -166,7 +205,7 @@ include third_party/nugget/common.mk await git.add(['main.c', 'Makefile', 'compile_flags.txt']) } -async function createPsyQProject(fullPath, name, progressReporter, tools) { +async function createPsyQProject (fullPath, name, progressReporter, tools) { const git = await createSkeleton(fullPath, name, progressReporter) await fs.writeFile( @@ -358,7 +397,7 @@ include third_party/nugget/common.mk await git.add(['main.c', 'Makefile', 'compile_flags.txt']) } -async function createPSYQoProject(fullPath, name, progressReporter) { +async function createPSYQoProject (fullPath, name, progressReporter) { const git = await createSkeleton(fullPath, name, progressReporter) await fs.writeFile( @@ -468,6 +507,7 @@ include third_party/nugget/psyqo/psyqo.mk const templates = { empty: { name: 'Empty', + category: 'Bare metal', description: 'An empty project, with just the barebone setup to get started.', url: 'https://github.com/pcsx-redux/nugget/blob/main/doc/README.md', @@ -476,20 +516,22 @@ const templates = { recommendedTools: ['gdb', 'debugger', 'redux'], create: createEmptyProject }, - psyq: { - name: 'Psy-Q SDK', + psyq_cube: { + name: 'Psy-Q Cube', + category: 'Psy-Q SDK', description: - 'A project using the Psy-Q SDK. Please note that while it is probably considered abandonware at this point, you will not receive a proper license from Sony. Use it at your own risk. Additionally, while the project folder on your harddrive will have the SDK installed on it, the created git repository will not. If you publish the created git repository, users who clone it will need to restore the SDK using the WELCOME page button.', + 'A project showing a spinning cube using the Psy-Q SDK. Please note that while it is probably considered abandonware at this point, you will not receive a proper license from Sony. Use it at your own risk. Additionally, while the project folder on your harddrive will have the SDK installed on it, the created git repository will not. If you publish the created git repository, users who clone it will need to restore the SDK using the WELCOME page button.', url: 'https://psx.arthus.net/sdk/Psy-Q/DOCS/', examples: 'https://github.com/ABelliqueux/nolibgs_hello_worlds', requiredTools: ['git', 'make', 'toolchain', 'psyq'], recommendedTools: ['gdb', 'debugger', 'redux'], create: createPsyQProject }, - psyqo: { - name: 'PSYQo SDK', + psyqo_hello: { + name: 'PSYQo Hello World', + category: 'PSYQo SDK', description: - 'A project using the PSYQo SDK. The PSYQo library is a C++-20 MIT-licensed framework cleanly written from scratch, allowing you to write modern, readable code targetting the PlayStation 1, while still being efficient. Additionally, you will have access to the EASTL library, which is a BSD-3-Clause licensed implementation of the C++ Standard Template Library.', + 'A project simply displaying Hello World using the PSYQo SDK. The PSYQo library is a C++-20 MIT-licensed framework cleanly written from scratch, allowing you to write modern, readable code targetting the PlayStation 1, while still being efficient. Additionally, you will have access to the EASTL library, which is a BSD-3-Clause licensed implementation of the C++ Standard Template Library.', url: 'https://github.com/pcsx-redux/nugget/tree/main/psyqo#how', examples: 'https://github.com/grumpycoders/pcsx-redux/tree/main/src/mips/psyqo/examples', @@ -521,6 +563,9 @@ exports.createProjectFromTemplate = async function (tools, options) { if (await fs.exists(fullPath)) { throw new Error('The project directory already exists.') } + if (fullPath.includes(' ')) { + throw new Error('The project path cannot contain spaces.') + } let resolver let rejecter const { progressReporter, progressResolver } = diff --git a/tools/vscode-extension/tools.js b/tools/vscode-extension/tools.js index 84f2bd855..123574343 100644 --- a/tools/vscode-extension/tools.js +++ b/tools/vscode-extension/tools.js @@ -14,19 +14,19 @@ const { Octokit } = require('@octokit/rest') const octokit = new Octokit() const os = require('node:os') -const mipsVersion = '13.1.0' +const mipsVersion = '13.2.0' let extensionUri let globalStorageUri let requiresReboot = false -async function checkInstalled(name) { +async function checkInstalled (name) { if (tools[name].installed === undefined) { tools[name].installed = await tools[name].check() } return tools[name].installed } -function checkSimpleCommand(command) { +function checkSimpleCommand (command) { return new Promise((resolve) => { execAsync(command, (error) => { if (error) { @@ -41,7 +41,7 @@ function checkSimpleCommand(command) { let mipsInstalling = false let win32MipsToolsInstalling = false -async function installMips() { +async function installMips () { if (mipsInstalling) return mipsInstalling = true try { @@ -60,7 +60,7 @@ async function installMips() { } } -async function installToolchain() { +async function installToolchain () { switch (process.platform) { case 'win32': try { @@ -166,14 +166,14 @@ async function installToolchain() { } } -function checkToolchain() { +function checkToolchain () { return Promise.any([ exec('mipsel-linux-gnu-g++ --version'), exec('mipsel-none-elf-g++ --version') ]) } -async function installGDB() { +async function installGDB () { switch (process.platform) { case 'win32': try { @@ -243,7 +243,7 @@ async function installGDB() { } } -async function installMake() { +async function installMake () { switch (process.platform) { case 'win32': try { @@ -288,7 +288,7 @@ async function installMake() { } } -async function installGit() { +async function installGit () { switch (process.platform) { case 'win32': { const release = await octokit.rest.repos.getLatestRelease({ @@ -326,7 +326,7 @@ async function installGit() { } } -function unpackPsyq(destination) { +function unpackPsyq (destination) { const filename = vscode.Uri.joinPath( globalStorageUri, tools.psyq.filename @@ -441,7 +441,7 @@ const tools = { } } -function checkLocalFile(filename) { +function checkLocalFile (filename) { return new Promise((resolve) => { filename = vscode.Uri.joinPath(globalStorageUri, filename).fsPath fs.access(filename, fs.constants.F_OK, (err) => { @@ -450,7 +450,7 @@ function checkLocalFile(filename) { }) } -function checkGDB() { +function checkGDB () { if (process.platform === 'darwin') return checkSimpleCommand('gdb --version') return checkSimpleCommand('gdb-multiarch --version') } @@ -511,7 +511,7 @@ exports.install = async (toInstall, force) => { exports.maybeInstall = async (toInstall) => { const installed = await checkInstalled(toInstall) if (!installed && !requiresReboot) { - const ret = exports.install([toInstall]) + const ret = exports.install([toInstall]) win32MipsToolsInstalling = false return ret } diff --git a/tools/win32-mips/Dockerfile b/tools/win32-mips/Dockerfile index 2639517b9..0b3c7565f 100644 --- a/tools/win32-mips/Dockerfile +++ b/tools/win32-mips/Dockerfile @@ -1,6 +1,6 @@ # escape=` -# Dockerfile to generate the Windows g++-mipsel-none-elf-13.1.0.zip package. +# Dockerfile to generate the Windows g++-mipsel-none-elf-13.2.0.zip package. FROM mcr.microsoft.com/windows/servercore:ltsc2019 WORKDIR C:\windows\temp @@ -45,41 +45,41 @@ RUN C:\msys64\usr\bin\bash.exe -l -c 'pacman -S --needed --noconfirm mingw-w64-x RUN C:\msys64\usr\bin\bash.exe -l -c 'pacman -S --needed --noconfirm mingw-w64-x86_64-python mingw-w64-x86_64-readline' RUN C:\msys64\usr\bin\bash.exe -l -c 'pacman -Scc --noconfirm' -ARG BINUTILS=https://ftp.gnu.org/gnu/binutils/binutils-2.40.tar.xz -ARG GCC=https://ftp.gnu.org/gnu/gcc/gcc-13.1.0/gcc-13.1.0.tar.xz +ARG BINUTILS=https://ftp.gnu.org/gnu/binutils/binutils-2.41.tar.xz +ARG GCC=https://ftp.gnu.org/gnu/gcc/gcc-13.2.0/gcc-13.2.0.tar.xz RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; ` - Invoke-WebRequest $env:BINUTILS -OutFile "C:\Windows\Temp\binutils-2.40.tar.xz"; ` - Start-Process -FilePath "C:\7-Zip\7z.exe" -ArgumentList e, "C:\Windows\Temp\binutils-2.40.tar.xz", `-y, `-oC:\Windows\Temp\ -NoNewWindow -PassThru -Wait; ` - Start-Process -FilePath "C:\7-Zip\7z.exe" -ArgumentList x, "C:\Windows\Temp\binutils-2.40.tar", `-y, `-oC:\ -NoNewWindow -PassThru -Wait; ` + Invoke-WebRequest $env:BINUTILS -OutFile "C:\Windows\Temp\binutils-2.41.tar.xz"; ` + Start-Process -FilePath "C:\7-Zip\7z.exe" -ArgumentList e, "C:\Windows\Temp\binutils-2.41.tar.xz", `-y, `-oC:\Windows\Temp\ -NoNewWindow -PassThru -Wait; ` + Start-Process -FilePath "C:\7-Zip\7z.exe" -ArgumentList x, "C:\Windows\Temp\binutils-2.41.tar", `-y, `-oC:\ -NoNewWindow -PassThru -Wait; ` Remove-Item @('C:\Windows\Temp\*', 'C:\Users\*\Appdata\Local\Temp\*') -Force -Recurse; RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; ` - Invoke-WebRequest $env:GCC -OutFile "C:\Windows\Temp\gcc-13.1.0.tar.xz"; ` - Start-Process -FilePath "C:\7-Zip\7z.exe" -ArgumentList e, "C:\Windows\Temp\gcc-13.1.0.tar.xz", `-y, `-oC:\Windows\Temp\ -NoNewWindow -PassThru -Wait; ` - Start-Process -FilePath "C:\7-Zip\7z.exe" -ArgumentList x, "C:\Windows\Temp\gcc-13.1.0.tar", `-y, `-oC:\ -NoNewWindow -PassThru -Wait; ` + Invoke-WebRequest $env:GCC -OutFile "C:\Windows\Temp\gcc-13.2.0.tar.xz"; ` + Start-Process -FilePath "C:\7-Zip\7z.exe" -ArgumentList e, "C:\Windows\Temp\gcc-13.2.0.tar.xz", `-y, `-oC:\Windows\Temp\ -NoNewWindow -PassThru -Wait; ` + Start-Process -FilePath "C:\7-Zip\7z.exe" -ArgumentList x, "C:\Windows\Temp\gcc-13.2.0.tar", `-y, `-oC:\ -NoNewWindow -PassThru -Wait; ` Remove-Item @('C:\Windows\Temp\*', 'C:\Users\*\Appdata\Local\Temp\*') -Force -Recurse; ENV MSYSTEM MINGW64 -RUN C:\msys64\usr\bin\bash.exe -l -c 'cd /c/binutils-2.40 && /c/binutils-2.40/configure --target=mipsel-none-elf --disable-multilib --disable-nls --disable-werror --prefix=/DIST || (cat /BUILD/config.log && exit 1)' +RUN C:\msys64\usr\bin\bash.exe -l -c 'cd /c/binutils-2.41 && /c/binutils-2.41/configure --target=mipsel-none-elf --disable-multilib --disable-nls --disable-werror --prefix=/DIST || (cat /BUILD/config.log && exit 1)' -RUN C:\msys64\usr\bin\bash.exe -l -c 'cd /c/binutils-2.40/libiberty && sed -i s/\\\\buint\\\\b/unsigned/ rust-demangle.c' +RUN C:\msys64\usr\bin\bash.exe -l -c 'cd /c/binutils-2.41/libiberty && sed -i s/\\\\buint\\\\b/unsigned/ rust-demangle.c' -RUN C:\msys64\usr\bin\bash.exe -l -c 'make -C /c/binutils-2.40 all -j8' -RUN C:\msys64\usr\bin\bash.exe -l -c 'make -C /c/binutils-2.40 install-strip' -RUN C:\msys64\usr\bin\bash.exe -l -c 'cp /c/binutils-2.40/COPYING* /DIST' +RUN C:\msys64\usr\bin\bash.exe -l -c 'make -C /c/binutils-2.41 all -j8' +RUN C:\msys64\usr\bin\bash.exe -l -c 'make -C /c/binutils-2.41 install-strip' +RUN C:\msys64\usr\bin\bash.exe -l -c 'cp /c/binutils-2.41/COPYING* /DIST' -RUN C:\msys64\usr\bin\bash.exe -l -c 'mkdir /c/gcc-13.1.0/build && cd /c/gcc-13.1.0/build && ../configure --target=mipsel-none-elf --without-isl --disable-nls --disable-threads --disable-shared --disable-libssp --disable-libstdcxx-pch --disable-libgomp --disable-werror --without-headers --disable-hosted-libstdcxx --with-as=/DIST/bin/mipsel-none-elf-as --with-ld=/DIST/bin/mipsel-none-elf-ld --enable-languages=c,c++ --prefix=/DIST || (cat /BUILD/config.log && exit 1)' -RUN C:\msys64\usr\bin\bash.exe -l -c 'make -C /c/gcc-13.1.0/build all-gcc -j8' -RUN C:\msys64\usr\bin\bash.exe -l -c 'make -C /c/gcc-13.1.0/build all-target-libgcc -j8' -RUN C:\msys64\usr\bin\bash.exe -l -c 'make -C /c/gcc-13.1.0/build all-target-libstdc++-v3 -j8' -RUN C:\msys64\usr\bin\bash.exe -l -c 'make -C /c/gcc-13.1.0/build install-strip-gcc install-strip-target-libgcc install-strip-target-libstdc++-v3' +RUN C:\msys64\usr\bin\bash.exe -l -c 'mkdir /c/gcc-13.2.0/build && cd /c/gcc-13.2.0/build && ../configure --target=mipsel-none-elf --without-isl --disable-nls --disable-threads --disable-shared --disable-libssp --disable-libstdcxx-pch --disable-libgomp --disable-werror --without-headers --disable-hosted-libstdcxx --with-as=/DIST/bin/mipsel-none-elf-as --with-ld=/DIST/bin/mipsel-none-elf-ld --enable-languages=c,c++ --prefix=/DIST || (cat /BUILD/config.log && exit 1)' +RUN C:\msys64\usr\bin\bash.exe -l -c 'make -C /c/gcc-13.2.0/build all-gcc -j4' +RUN C:\msys64\usr\bin\bash.exe -l -c 'make -C /c/gcc-13.2.0/build all-target-libgcc -j4' +RUN C:\msys64\usr\bin\bash.exe -l -c 'make -C /c/gcc-13.2.0/build all-target-libstdc++-v3 -j4' +RUN C:\msys64\usr\bin\bash.exe -l -c 'make -C /c/gcc-13.2.0/build install-strip-gcc install-strip-target-libgcc install-strip-target-libstdc++-v3' RUN C:\msys64\usr\bin\bash.exe -l -c 'for t in cat cp echo mkdir rm touch which ; do cp /usr/bin/$t.exe /DIST/bin ; done' RUN C:\msys64\usr\bin\bash.exe -l -c 'cp /mingw64/bin/mingw32-make.exe /DIST/bin/make.exe' RUN C:\msys64\usr\bin\bash.exe -l -c 'cd /DIST && find . -name *.exe | while read bin ; do ldd $bin | cut -f2 -d\> | cut -f2 -d\ | grep -v /c/Windows/S | while read f ; do cp $f $(dirname $bin) ; done ; done || true' -RUN C:\msys64\usr\bin\bash.exe -l -c 'cd /DIST && zip /c/g++-mipsel-none-elf-13.1.0.zip . -r' +RUN C:\msys64\usr\bin\bash.exe -l -c 'cd /DIST && zip /c/g++-mipsel-none-elf-13.2.0.zip . -r' CMD C:\msys64\usr\bin\bash.exe -l diff --git a/vsprojects/Lua/Lua.vcxproj b/vsprojects/Lua/Lua.vcxproj index 404ce523e..46456a461 100644 --- a/vsprojects/Lua/Lua.vcxproj +++ b/vsprojects/Lua/Lua.vcxproj @@ -41,6 +41,7 @@ + @@ -49,6 +50,7 @@ + @@ -66,6 +68,7 @@ + @@ -281,11 +284,13 @@ + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + \ No newline at end of file diff --git a/vsprojects/Lua/Lua.vcxproj.filters b/vsprojects/Lua/Lua.vcxproj.filters index a108d0842..ae9386e20 100644 --- a/vsprojects/Lua/Lua.vcxproj.filters +++ b/vsprojects/Lua/Lua.vcxproj.filters @@ -33,6 +33,9 @@ Source Files + + Source Files + @@ -53,6 +56,9 @@ Header Files + + Header Files + @@ -107,5 +113,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/vsprojects/Lua/packages.config b/vsprojects/Lua/packages.config index 767506c2a..2cf23bf0f 100644 --- a/vsprojects/Lua/packages.config +++ b/vsprojects/Lua/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/vsprojects/common.props b/vsprojects/common.props index c5dcfb05e..1dca85d3f 100644 --- a/vsprojects/common.props +++ b/vsprojects/common.props @@ -10,7 +10,7 @@ true $(SolutionDir)..\;$(SolutionDir)..\src;$(SolutionDir)..\third_party;$(SolutionDir)..\third_party\curl\include;$(SolutionDir)..\third_party\ConcurrentQueue;$(SolutionDir)..\third_party\capstone\include\capstone;$(SolutionDir)..\third_party\googletest\googletest\include;$(SolutionDir)..\third_party\ELFIO;$(SolutionDir)..\third_party\expected\include;$(SolutionDir)..\third_party\fmt\include;$(SolutionDir)..\third_party\freetype\include;$(SolutionDir)..\third_party\gl3w;$(SolutionDir)..\third_party\imgui;$(SolutionDir)..\third_party\imgui\backends;$(SolutionDir)..\third_party\imgui\misc\cpp;$(SolutionDir)..\third_party\libuv\include;$(SolutionDir)..\third_party\libuv\src;$(SolutionDir)..\third_party\md4c\src;$(SolutionDir)..\third_party\SDL\include;$(SolutionDir)..\third_party\uvw\src;$(SolutionDir)..\third_party\xbyak\xbyak;$(SolutionDir)..\third_party\zep\extensions;$(SolutionDir)..\third_party\zep\include;$(SolutionDir)..\third_party\zlib;%(AdditionalIncludeDirectories) stdcpplatest - IMGUI_ENABLE_FREETYPE;IMGUI_DISABLE_OBSOLETE_KEYIO;HAVE_STDINT_H;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;_GNU_SOURCE;NOMINMAX;WIN32;_WINDOWS;_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING;ZEP_FEATURE_CPP_FILE_SYSTEM;CURL_STATICLIB;BUILDING_LIBCURL;USE_WIN32_IDN;WANT_IDN_PROTOTYPES;USE_IPV6;USE_WINDOWS_SSPI;USE_SCHANNEL;PB_STATIC_API;%(PreprocessorDefinitions) + IMGUI_ENABLE_FREETYPE;IMGUI_DISABLE_OBSOLETE_KEYIO;HAVE_STDINT_H;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;_GNU_SOURCE;NOMINMAX;WIN32;_WINDOWS;_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING;ZEP_FEATURE_CPP_FILE_SYSTEM;CURL_STATICLIB;BUILDING_LIBCURL;USE_WIN32_IDN;WANT_IDN_PROTOTYPES;USE_IPV6;USE_WINDOWS_SSPI;USE_SCHANNEL;PB_STATIC_API;WINVER=0x0602;_WIN32_WINNT=0x0602;%(PreprocessorDefinitions) false false 4244;%(DisableSpecificWarnings) diff --git a/vsprojects/gtest/gtest.vcxproj b/vsprojects/gtest/gtest.vcxproj index c359326c7..74837b321 100644 --- a/vsprojects/gtest/gtest.vcxproj +++ b/vsprojects/gtest/gtest.vcxproj @@ -370,7 +370,16 @@ true true - + + true + true + true + true + true + true + true + true + diff --git a/vsprojects/gui/gui.vcxproj b/vsprojects/gui/gui.vcxproj index 597da4b62..5dc224e69 100644 --- a/vsprojects/gui/gui.vcxproj +++ b/vsprojects/gui/gui.vcxproj @@ -312,6 +312,7 @@ + @@ -319,5 +320,6 @@ + \ No newline at end of file diff --git a/vsprojects/gui/packages.config b/vsprojects/gui/packages.config index 28fbf8f44..e4d365855 100644 --- a/vsprojects/gui/packages.config +++ b/vsprojects/gui/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file diff --git a/vsprojects/lpeg/lpeg.vcxproj b/vsprojects/lpeg/lpeg.vcxproj new file mode 100644 index 000000000..7a0866889 --- /dev/null +++ b/vsprojects/lpeg/lpeg.vcxproj @@ -0,0 +1,299 @@ + + + + + Debug + Win32 + + + ReleaseWithClangCL + Win32 + + + ReleaseWithClangCL + x64 + + + ReleaseWithTracy + Win32 + + + ReleaseWithTracy + x64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {ce54ed92-4645-4ae9-bdc8-c0b9607765f8} + lpeg + 10.0 + + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + false + ClangCL + true + Unicode + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + false + ClangCL + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/vsprojects/lpeg/lpeg.vcxproj.filters b/vsprojects/lpeg/lpeg.vcxproj.filters new file mode 100644 index 000000000..6f1ec7db4 --- /dev/null +++ b/vsprojects/lpeg/lpeg.vcxproj.filters @@ -0,0 +1,75 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + + + + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/vsprojects/lpeg/packages.config b/vsprojects/lpeg/packages.config new file mode 100644 index 000000000..767506c2a --- /dev/null +++ b/vsprojects/lpeg/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/vsprojects/mainthunk/mainthunk.vcxproj b/vsprojects/mainthunk/mainthunk.vcxproj index 0f406c3dd..373cbda15 100644 --- a/vsprojects/mainthunk/mainthunk.vcxproj +++ b/vsprojects/mainthunk/mainthunk.vcxproj @@ -440,6 +440,9 @@ {4b88e4f6-56b3-4f66-bee8-0a4a21937bee} + + {ce54ed92-4645-4ae9-bdc8-c0b9607765f8} + {f0dabab6-069e-4b31-9bfc-296ce2fe23a6} diff --git a/vsprojects/pcsx-redux.sln b/vsprojects/pcsx-redux.sln index f3934ec5e..3d2f1ed5c 100644 --- a/vsprojects/pcsx-redux.sln +++ b/vsprojects/pcsx-redux.sln @@ -95,6 +95,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "exe2elf", "exe2elf\exe2elf. EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "supportpsx", "supportpsx\supportpsx.vcxproj", "{B2E2AD84-9D7F-4976-9572-E415819FFD7F}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lpeg", "lpeg\lpeg.vcxproj", "{CE54ED92-4645-4AE9-BDC8-C0B9607765F8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -474,6 +476,16 @@ Global {B2E2AD84-9D7F-4976-9572-E415819FFD7F}.ReleaseWithClangCL|x64.Build.0 = ReleaseWithClangCL|x64 {B2E2AD84-9D7F-4976-9572-E415819FFD7F}.ReleaseWithTracy|x64.ActiveCfg = ReleaseWithTracy|x64 {B2E2AD84-9D7F-4976-9572-E415819FFD7F}.ReleaseWithTracy|x64.Build.0 = ReleaseWithTracy|x64 + {CE54ED92-4645-4AE9-BDC8-C0B9607765F8}.Debug|x64.ActiveCfg = Debug|x64 + {CE54ED92-4645-4AE9-BDC8-C0B9607765F8}.Debug|x64.Build.0 = Debug|x64 + {CE54ED92-4645-4AE9-BDC8-C0B9607765F8}.Release|x64.ActiveCfg = Release|x64 + {CE54ED92-4645-4AE9-BDC8-C0B9607765F8}.Release|x64.Build.0 = Release|x64 + {CE54ED92-4645-4AE9-BDC8-C0B9607765F8}.ReleaseCLI|x64.ActiveCfg = ReleaseWithClangCL|x64 + {CE54ED92-4645-4AE9-BDC8-C0B9607765F8}.ReleaseCLI|x64.Build.0 = ReleaseWithClangCL|x64 + {CE54ED92-4645-4AE9-BDC8-C0B9607765F8}.ReleaseWithClangCL|x64.ActiveCfg = ReleaseWithClangCL|x64 + {CE54ED92-4645-4AE9-BDC8-C0B9607765F8}.ReleaseWithClangCL|x64.Build.0 = ReleaseWithClangCL|x64 + {CE54ED92-4645-4AE9-BDC8-C0B9607765F8}.ReleaseWithTracy|x64.ActiveCfg = ReleaseWithTracy|x64 + {CE54ED92-4645-4AE9-BDC8-C0B9607765F8}.ReleaseWithTracy|x64.Build.0 = ReleaseWithTracy|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -517,9 +529,10 @@ Global {4105DDD2-39FC-49EF-BBD7-1C64BCFC64AB} = {C6DD47BC-0C38-4AE6-B517-9675F3AC8A50} {CDED480F-14EE-475E-97F5-97F2B62DB3CE} = {C6DD47BC-0C38-4AE6-B517-9675F3AC8A50} {B2E2AD84-9D7F-4976-9572-E415819FFD7F} = {008A2872-432F-480B-828D-FF9AAA4846BC} + {CE54ED92-4645-4AE9-BDC8-C0B9607765F8} = {64A05F50-3203-42CC-B632-09D6EE6EA856} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {284E91C1-764E-441A-854D-CFD3623A6501} SolutionGuid = {AC54A867-F976-4B3D-A6EF-F57EB764DCD4} + SolutionGuid = {284E91C1-764E-441A-854D-CFD3623A6501} EndGlobalSection EndGlobal diff --git a/vsprojects/tests/pcsxrunner/pcsxrunner.vcxproj b/vsprojects/tests/pcsxrunner/pcsxrunner.vcxproj index a750ae6cb..9e0200976 100644 --- a/vsprojects/tests/pcsxrunner/pcsxrunner.vcxproj +++ b/vsprojects/tests/pcsxrunner/pcsxrunner.vcxproj @@ -336,6 +336,9 @@ {4b88e4f6-56b3-4f66-bee8-0a4a21937bee} + + {ce54ed92-4645-4ae9-bdc8-c0b9607765f8} + {f0dabab6-069e-4b31-9bfc-296ce2fe23a6}